DDD(またはセンス)との関係をモデル化しますか?


9

簡略化された要件は次のとおりです。

ユーザーがQuestion複数Answerのでを作成します。Question少なくとも1つ必要Answerです。

明確化:考えるQuestionAnswer同様の試験:1つの質問がありますが、いくつかの答えは、どこ少数の正しいかもしれません。ユーザーはこのテストを準備している俳優なので、質問と回答を作成します。

この単純な例をモデル化して、1)実際のモデルと一致させ、2)コードで表現力を高め、誤用やエラーの可能性を最小限に抑え、開発者にモデルの使用方法のヒントを与えるようにしています。

質問はエンティティですが、回答は値オブジェクトです。質問は答えを保持します。これまでのところ、私はこれらの可能な解決策を持っています。

【A】工場内Question

Answer手動で作成する代わりに、以下を呼び出すことができます。

Answer answer = question.createAnswer()
answer.setText("");
...

これで回答が作成され、質問追加されます。次に、プロパティを設定して回答を操作できます。このようにして、質問のみが回答を作成できます。また、迷わず回答させていただくこともございます。ただし、回答はでハードコーディングされているため、回答の作成を制御することはできませんQuestion

上記のコードの「言語」には1つの問題もあります。ユーザーは質問ではなく回答を作成する人です。個人的には、値オブジェクトを作成するのが好きではありません。開発者に依存して値を入力します-どのように追加する必要があるかをどのように確認できますか?

[B]質問の中のファクトリー、#2を取る

この種のメソッドはQuestion次のようにすべきだと言う人もいます:

question.addAnswer(String answer, boolean correct, int level....);

上記のソリューションと同様に、このメソッドは回答の必須データを取得し、質問にも追加されるデータを作成します。

ここでの問題は、正当な理由なくのコンストラクタを複製するAnswerことです。また、質問は本当に答えを作成しますか?

[C]コンストラクターの依存関係

両方のオブジェクトを自分で自由に作成してみましょう。依存関係の権利をコンストラクタで表現しましょう:

Question q = new Question(...);
Answer a = new Answer(q, ...);   // answer can't exist without a question

質問がないと回答を作成できないため、これは開発者にヒントを与えます。ただし、質問に回答が「追加」されるという「言語」は見当たりません。一方、本当に見る必要がありますか?

[D]コンストラクターの依存関係、2番を取る

反対のことができます:

Answer a1 = new Answer("",...);
Answer a2 = new Answer("",...);
Question q = new Question("", a1, a2);

これは上記の逆の状況です。ここでは、回答は質問なしで存在できます(意味がありません)が、質問は回答なしで存在できません(意味があります)。また、ここでの「言語」は、質問が答えを持っていることについてより明確です。

[E]一般的な方法

これは私が一般的な方法と呼んでいるもので、pplが通常最初に行うことです。

Question q = new Question("",...);
Answer a = new Answer("",...);
q.addAnswer(a);

これは、上記の2つの回答の「ルーズ」バージョンです。回答と質問の両方が互いに存在しなくてもよいためです。あなたがいることを特別なヒントはありません持って、それらを一緒にバインドするためには。

[F]結合

または、C、D、Eを組み合わせて、関係を構築する方法をすべてカバーし、開発者が最適なものを使用できるようにする必要があります。

質問

私は人々が「直感」に基づいて上記の答えの1つを選択するかもしれないことを知っています。しかし、上記のバリアントのいずれかが他のバリアントより優れているかどうかは、そのための正当な理由があります。また、上記の質問については考えないでください。ここでは、ほとんどの場合に適用できるいくつかのベストプラクティスを絞り込みます。同意すると、一部のエンティティの作成のほとんどのユースケースは似ています。また、ここではテクノロジーにとらわれません。ORMを使用するかどうかは考えたくありません。ちょうど良い、表現力豊かなモードが欲しい。

これに関する知恵はありますか?

編集

Questionおよびの他のプロパティは無視してくださいAnswer。これらは質問には関係ありません。上記のテキストを編集し、ほとんどのコンストラクター(必要な場合)を変更しました。これで、必要なプロパティ値を受け入れるようになりました。それは単なる質問文字列、またはさまざまな言語、ステータスなどの文字列のマップである可能性があります-渡されるプロパティはすべて、このための焦点では​​ありません;)したがって、異なることが述べられていない限り、必要なパラメータを渡すことを上回っていると仮定します。ありがとう!

回答:


6

更新しました。考慮された説明。

これは複数選択ドメインであるように見えますが、通常は次の要件があります

  1. 質問を選択するには、少なくとも2つの選択肢が必要です
  2. 少なくとも1つの正しい選択が必要です
  3. 質問なくして選択肢はないはずです

上記に基づいて

[A]ポイント1からの不変量を保証できないため、選択の余地のない質問になる可能性があります

[B]に[A]と同じ欠点があります

[C]に[A][B]と同じ欠点があります

[D]は有効なアプローチですが、選択肢を個別に渡すのではなく、リストとして渡すことをお勧めします

[E]に[A][B]、および[C]と同じ欠点があります

したがって、[D]を選択するのは、ポイント1、2、3からのドメインルールが確実に守られるようにするためです。質問が何の選択肢もないまま長期間続く可能性は非常に低いと言ったとしても、コードを通じてドメイン要件を伝えることは常に良い考えです。

私はまた、名前を変更するだろうAnswerChoice、それは、このドメイン内の私には、より理にかなっているよう。

public class Choice implements ValueObject {

    private Question q;
    private final String txt;
    private final boolean isCorrect;
    private boolean isSelected = false;

    public Choice(String txt, boolean isCorrect) {
        // validate and assign
    }

    public void assignToQuestion(Question q) {
        this.q = q;
    }

    public void select() {
        isSelected = true;
    }

    public void unselect() {
        isSelected = false;
    }

    public boolean isSelected() {
        return isSelected;
    }
}

public class Question implements Entity {

    private final String txt;
    private final List<Choice> choices;

    public Question(String txt, List<Choice> choices) {
        // ensure requirements are met
        // 1. make sure there are more than 2 choices
        // 2. make sure at least 1 of the choices is correct
        // 3. assign each choice to this question
    }
}

Choice ch1 = new Choice("The sky", false);
Choice ch2 = new Choice("Ceiling", true);
List<Choice> choices = Arrays.asList(ch1, ch2);
Question q = new Question("What's up?", choices);

メモ。あなたが作る場合はQuestion、エンティティに集約ルートとChoice値オブジェクト同じ集合体の一部を、1を格納することができます何のチャンスをありませんChoice、それが割り当てされることなく、Questionあなたが直接参照を渡すことはありませんが(Questionへの引数としてChoiceのコンストラクタ)。リポジトリはルートでのみ機能し、ビルドするQuestionと、コンストラクタですべての選択肢が割り当てられます。

お役に立てれば。

更新

質問の前に選択肢を作成する方法が本当に気になる場合は、役立つと思われるいくつかのトリックがあります

1)質問の後に、または少なくとも同時に作成されたようにコードを再配置します

Question q = new Question(
    "What's up?",
    Arrays.asList(
        new Choice("The sky", false),
        new Choice("Ceiling", true)
    )
);

2)コンストラクタを非表示にし、静的なファクトリメソッドを使用する

public class Question implements Entity {
    ...

    private Question(String txt) { ... }

    public static Question newInstance(String txt, List<Choice> choices) {
        Question q = new Question(txt);
        for (Choice ch : choices) {
            q.assignChoice(ch);
        }
    }

    public void assignChoice(Choice ch) { ... }
    ...
}

3)ビルダーパターンを使用する

Question q = new Question.Builder("What's up?")
    .assignChoice(new Choice("The sky", false))
    .assignChoice(new Choice("Ceiling", true))
    .build();

ただし、すべてはドメインによって異なります。ほとんどの場合、オブジェクトの作成順序は、問題ドメインの観点からは重要ではありません。さらに重要なことは、クラスのインスタンスを取得するとすぐに、論理的に完成し、使用できるようになることです。


時代遅れ。以下のすべては、明確化後の質問には無関係です。

まず、DDDドメインモデルによると、現実の世界では理にかなっているはずです。したがって、いくつかの点

  1. 質問には答えがない場合があります
  2. 質問なくして答えはないはずです
  3. 答えは正確に1つの質問に対応する必要があります
  4. 「空の」答えは質問に答えません

上記に基づいて

[A]は誤用しやすく、テキストの設定を忘れやすいため、ポイント4と矛盾する場合があります。

[B]は有効なアプローチですが、オプションのパラメーターが必要です

[C]テキストなしで回答できるため、ポイント4と矛盾する可能性があります

[D]ポイント1と矛盾し、ポイント2と3と矛盾する場合があります

[E]ポイント2、3、4と矛盾する場合があります

次に、ドメインロジックを適用するためにOOP機能を利用できます。つまり、必要なパラメーターのコンストラクターとオプションのパラメーターのセッターを使用できます。

第三に、私はドメインにとってより自然であると思われるユビキタス言語を使用します。

そして最後に、集約ルート、エンティティ、値オブジェクトなどのDDDパターンを使用してすべてを設計できます。質問をその集合体のルートにし、回答をその一部にすることができます。答えは質問の文脈の外では意味がないため、これは論理的な決定です。

したがって、上記のすべてが次の設計に要約されます

class Answer implements ValueObject {

    private final Question q;
    private String txt;
    private boolean isCorrect = false;

    Answer(Question q, String txt) {
        // validate and assign
    }

    public void markAsCorrect() {
        isCorrect = true;
    }

    public boolean isCorrect() {
        return isCorrect;
    }
}

public class Question implements Entity {

    private String txt;
    private final List<Answer> answers = new ArrayList<>();

    public Question(String txt) {
        // validate and assign
    }

    // Ubiquitous Language: answer() instead of addAnswer()
    public void answer(String txt) {
        answers.add(new Answer(this, txt));
    }
}

Question q = new Question("What's up?");
q.answer("The sky");

PSあなたの質問への回答私はあなたのドメインについて正しくないかもしれないいくつかの仮定をしましたので、あなたの詳細で上記を自由に調整してください。


1
要約すると、これはBとCの混合です。要件の説明を参照してください。あなたのポイント1.質問を構築している間、「短い」期間だけ存在するかもしれません。データベースにはありません。その意味で、4。は絶対に起こらないはずです。私は今、要件が明確であることを願っています;)
法律家、2014年

ところで、明確にすると、私にとっては、addAnswerそれassignAnswerよりも優れた言語であると思わanswerれます。これに同意してください。とにかく、私の質問は-あなたはまだBに行き、例えば回答メソッドのほとんどの引数のコピーを持っていますか?それは重複ではないでしょうか?
法律家、2014年

要件が不明確で申し訳ありません。回答を更新していただけませんか。
法律家、2014年

1
私の仮定が間違っていたことがわかりました。私はあなたのQAドメインをstackexchangeウェブサイトの例として扱いましたが、それは多肢選択テストのように見えます。はい、回答を更新します。
zafarkhaja 2014年

1
@lawpert Answerは値オブジェクトであり、その集約の集約ルートとともに格納されます。値オブジェクトを直接保存したり、エンティティが集約のルートでない場合はエンティティを保存したりしません。
zafarkhaja 2014年

1

要件が非常に単純で、複数の可能なソリューションが存在する場合、KISSの原則に従う必要があります。あなたの場合、それはオプションEです。

何かを表現するコードを作成する場合もありますが、そうするべきではありません。たとえば、回答の作成を質問(AおよびB)に結び付けたり、回答の参照を質問(CおよびD)に指定したりすると、ドメインに不要な動作が追加され、混乱を招く可能性があります。また、あなたの場合、質問はおそらく回答と集約され、回答は値タイプになります。


1
なぜ[C]は不要な動作なのですか?私が見ているように、[C]はAnswerは質問なしでは生きられないことを伝えています。それがまさにそれです。さらに、Answerがさらに必須のフラグ(たとえば、回答のタイプ、カテゴリなど)を必要とする場合を想像してください。KISSに行くと、必須事項に関する知識が失われます。開発者は、回答を正しくするために、回答に追加/設定する必要があるものを前に知っておく必要があります。ここでの質問は、この非常に単純な例をモデル化することではなく、OOを使用してユビキタス言語を書くためのより良い実践を見つけることであったと私は信じています。
igor 2014年

@igor Eは、質問の回答をリポジトリに保存するために質問に回答を割り当てることを必須にすることで、回答が質問の一部であることをすでに伝えています。質問を読み込まずにAnswerだけを保存する方法があったら、Cの方が良いでしょう。しかし、それはあなたが書いたものから明らかではありません。
陶酔感

@igorまた、Answerの作成とQuestionを関連付けたい場合は、Aの方が適しています。Cを使用すると、Answerが質問に割り当てられたときに非表示になるためです。また、Aでテキストを読んで、「モデルの動作」とこの動作を開始するユーザーを区別する必要があります。質問は、何らかの方法で回答を初期化する必要がある場合に、回答を作成する責任があります。「ユーザーが回答を作成する」こととは関係ありません。
陶酔した

記録のためだけに、C&Eの間で引き裂かれた:)今、これは、「...保存する質問に回答を割り当てることを必須にすることによって、リポジトリです。」これは、「必須」の部分は、リポジトリにアクセスしたときにのみ発生することを意味します。そのため、必須の接続はコンパイル時に開発者に「見えない」ため、ビジネスルールがリポジトリにリークします。そのため、ここで[C]をテストしています。たぶん、この講演で、 Cオプションについての詳細を知ることができます。
igor 2014年

これは、「...回答の作成と質問を結び付けたい...」です。_creation自体を結び付けたくありません必須の関係を表現したいだけです(個人的には、可能であれば自分でモデルオブジェクトを作成できるようにしたい)。だから、私の見解では、これは作成についてではなく、それが私がすぐにAとBを捨てる理由です。質問が回答を作成する責任があるとは思いません。
igor 2014年

1

私は[C]または[E]のどちらかに行きます。

まず、なぜAとBではないのですか?私の質問が関連する価値を生み出す責任を負いたくない。Questionに他の多くの値オブジェクトがある場合を想像してみてください-それぞれにcreateメソッドを配置しますか?または、いくつかの複雑な集合体がある場合、同じケースです。

なぜ[D]じゃないの?それは私たちが自然に持っているものと反対だからです。最初に質問を作成します。これらすべてを作成するWebページを想像できます。ユーザーが最初に質問を作成しますよね?したがって、Dではありません。

[E]は@Euphoricが言ったようにKISSです。でも最近は[C]も好きになりました。見た目はそれほど混乱しません。さらに、Questionがより多くのものに依存している場合を想像してみてください。開発者は、正しく初期化するためにQuestion内に何を入れる必要があるかを知る必要があります。あなたの言う通りですが、答えが実際に質問に追加されることを説明する「視覚的な」言葉はありません。

追加の読書

このような質問は、私たちのコンピューター言語がモデリングには一般的すぎるのではないかと思います。(私はそれらすべてのプログラミング要件に答えるために一般的でなければならないことを理解してます)。最近、流暢なインターフェイスを使用してビジネス言語を表現するためのより良い方法を見つけようとしています。次のようなもの(sudo言語):

use(question).addAnswer(answer).storeToRepo();

つまり、大きな* Servicesおよび* Repositoryクラスからビジネスロジックの小さなチャンクに移動しようとしています。ただのアイデア。


アドオンでドメイン固有言語について話していますか?
法律家、2014年

さて、あなたが言ったとき、それはそう見えます:)購入私はそれについて重要な経験はありません。
igor

2
IOは直交する責任であり、したがってエンティティ(storeToRepo)で処理されるべきではないというコンセンサスがあると思います
Pedersen

私は@Esben Skov Pedersenに同意します。エンティティ自体は内部でrepoを呼び出すべきではありません(それはあなたが言ったことですよね?)。しかし、ここではAFAIUとして、コマンドを呼び出す背後にある種のビルダーパターンがあります。したがって、IOはここのエンティティでは行われません。少なくともこれは、
Iveが

正しい@lawpert。どのように機能するのかはわかりませんが、興味深いでしょう。
Esben Skov Pedersen 2014

1

ここでポイントを逃したと思います。集約ルートはテストエンティティでなければなりません。

そして、それが本当に当てはまる場合、TestFactoryがあなたの問題に答えるのに最も適していると思います。

質問と回答の構築をファクトリに委任するので、サブエンティティをインスタンス化する方法をクライアントに隠すので、モデルを壊すことなく、基本的に考えていたソリューションを使用できます。

これは、TestFactoryがTestのインスタンス化に使用する唯一のインターフェイスである限りです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.