3

メールをフォーマットするクラス BaseEmailTemplate があり、デフォルトを上書きできる派生型を作成したいと考えています。もともと私のベースコンストラクター -

public BaseEmailTemplate(Topic topic)
        {

                CreateAddresses(topic);
                CreateSubject(topic);
                CreateBody(topic);

        }

... (Body/Addresses)

protected virtual void CreateSubject(Topic topic)
    {
        Subject = string.Format("Base boring format: {0}", topic.Name);
    }

そして私の派生では

public NewEmailTemplate(Topic topic) : Base (topic)
        {

            //Do other things
        }

protected override void CreateSubject(Topic topic)
        {
            Subject = string.Format("New Topic: {0} - {1})", topic.Id, topic.Name);
        }

もちろん、これはここで説明されているエラーにつながります:コンストラクターでの仮想メンバー呼び出し

したがって、これについて率直に言うと、すべての派生型で同じメソッドを呼び出す必要はありません。反対に、一部/すべてを変更できる必要があります。別のベースには異なるアドレスのサブセットがあることは知っていますが、本文と件名がデフォルトになります。

3 つのメソッドすべてを呼び出す必要があり、それらのいずれかを変更する機能は、派生ごとに使用できる必要があります。

誰もが言っているように見えることは、仮想を使用することの意図しない結果であるように思われることを意味します。それは私の正確な意図のようです..

更新 - 明確化

コンストラクターの仮想メンバーが悪い理由を理解しています。そのトピックに関する回答に感謝しますが、私の質問は「なぜこれが悪いのですか?」ではありません。それは「わかりました、これは悪いことですが、何が自分のニーズに適しているのかわかりません。それで、どうすればよいですか?」

これが現在実装されている方法です

 private void SendNewTopic(TopicDTO topicDto)
        {
            Topic topic = Mapper.Map<TopicDTO , Topic>(topicDto);
            var newEmail = new NewEmailTemplate(topic);
            SendEmail(newEmail);  //Preexisting Template Reader infrastructure

            //Logging.....
        }

私は子供と孫を扱っています。私が入ったのは newemailtemplate だけでしたが、他に 4 つのテンプレートを作成する必要がありますが、コードの 90% は再利用可能です。そのため、BaseEmailTemplate(Topic topic) を作成することにしました。BaseTemplate は、Subject や List など、SendEmail が読み取ることを期待するものを作成します。

  NewEmailTemplate(Topic topic): BaseEmailTemplate(Topic topic): BaseTemplate, IEmailTempate

私の仕事をフォローしている人にそのことを知ってもらう必要がないことを望みます

 var newEmail = new NewEmailTemplate();
 newEmail.Init(topic);

使用するたびに必要です。オブジェクトはそれなしでは使用できません。それについて多くの警告があると思いましたか?

4

5 に答える 5

3

ファクトリ メソッドと初期化関数は、この状況に対する効果的な回避策です。

基本クラス:

private EmailTemplate()
{
   // private constructor to force the factory method to create the object
}

public static EmailTemplate CreateBaseTemplate(Topic topic)
{
    return (new BaseEmailTemplate()).Initialize(topic);
}

protected EmailTemplate Initialize(Topic topic)
{
   // ...call virtual functions here
   return this;
}

そして派生クラスでは:

public static EmailTemplate CreateDerivedTemplate(Topic topic)
{
    // You do have to copy/paste this initialize logic here, I'm afraid.
    return (new DerivedEmailTemplate()).Initialize(topic);
}

protected override CreateSubject...

オブジェクトを作成するための唯一の公開メソッドはファクトリ メソッドを使用するため、エンド ユーザーが初期化の呼び出しを忘れる心配はありません。さらに派生クラスを作成したい場合、拡張するのはそれほど簡単ではありませんが、オブジェクト自体は非常に使いやすいはずです。

于 2013-09-19T12:34:11.647 に答える
3

C# 仕様の [10.11] は、オブジェクト コンストラクターが最初に基本クラスから順に実行され、最後に最も継承されたクラスになることを示しています。一方、仕様の [10.6.3] は、実行時に実行される仮想メンバーの最も派生した実装であることを示しています。

Null Reference Exceptionこれが意味することは、派生オブジェクトのコンストラクターがまだ実行されていないため、派生クラスによって初期化された項目にアクセスする場合、ベース オブジェクト コンストラクターから派生メソッドを実行しようとすると、 を 受け取る可能性があるということです。

事実上、Base メソッドのコンストラクターは [10.11] を実行しCreateSubject()、コンストラクターが終了して派生コンストラクターを実行できるようになる前に派生メソッドを参照しようとするため、メソッドが疑わしいものになります。

前述のように、この場合、派生メソッドはパラメーターとして渡された項目のみに依存しているように見え、問題なく実行される可能性があります。

これは警告であり、それ自体はエラーではありませんが、実行時にエラーが発生する可能性があることを示していることに注意してください。

基本クラスのコンストラクター以外のコンテキストからメソッドが呼び出された場合、これは問題になりません。

于 2013-09-18T22:06:10.467 に答える
1

private readonly Topic _topic回避策として、コンストラクターを使用してフィールドを初期化し、3 つのメソッド呼び出しをprotected void Initialize()派生型がコンストラクターで安全に呼び出すことができるメソッドに移動することが考えられます。これは、その呼び出しが発生したときに基本コンストラクターが既に実行されているためです。

Initialize()怪しい部分は、派生型がその呼び出しを行うことを覚えておく必要があることです。

于 2013-09-18T22:16:22.630 に答える
0

この回答は、最近誰かがこの質問に出くわした場合に備えて、完全を期すためのものです (私のように)。

Init物事をシンプルに保ちながら別のメソッドを避けるために、コードのユーザーにとってより自然に感じることができる (IMO) ことの 1 つTopicは、基本クラスのプロパティとして持つことです。

// This:
var newEmail = new NewEmailTemplate { Topic = topic };

// Instead of this:
var newEmail = new NewEmailTemplate();
newEmail.Init(topic);

次に、プロパティ セッターは、次のような抽象メソッドの呼び出しを処理できます。

public abstract class BaseEmailTemplate
{
    // No need for even a constructor

    private Topic topic;

    public Topic
    {
        get => topic;
        set
        {
            if (topic == value)
            {
                return;
            }

            topic = value;

            // Derived methods could also access the topic
            // as this.Topic instead of as an argument
            CreateAddresses(topic);
            CreateSubject(topic);
            CreateBody(topic);
        }
    }

    protected abstract void CreateAddresses(Topic topic);

    protected abstract void CreateSubject(Topic topic);

    protected abstract void CreateBody(Topic topic);
}

長所:

  • 電子メール テンプレートは、直感的な構文を使用して 1 行で定義できます。
  • ファクトリ メソッドや 3 番目のクラスは含まれません
  • 派生クラスは、抽象メソッドのオーバーライドについてのみ心配する必要があり、基本コンストラクターの呼び出しについて心配する必要はありません (ただし、他の変数をコンストラクター引数として渡したい場合もあります)。

短所:

  • ユーザーが の定義を忘れる可能性を考慮し、Topicnull の場合を処理する必要があります。しかし、とにかくそうするべきだと私は主張します。誰かが null トピックを元のコンストラクターに明示的に渡すことができた
  • Topic実際には必要なく、プロパティを公開しています。とにかくこれを行うつもりだったかもしれませんが、そうでない場合は、あまり理想的ではないかもしれません。ゲッターを削除することもできますが、それは少し奇妙に見えるかもしれません
  • 相互に依存するプロパティが複数ある場合は、ボイラープレート コードが増加します。これらすべてを単一のクラスにグループ化して、1 つのセッターのみが抽象メソッドをトリガーするようにすることができます。
于 2019-02-22T16:07:06.267 に答える