DDD (veya duyu ile) ile model ilişkileri?


9

İşte basitleştirilmiş bir gereksinim:

Kullanıcı Questionbirden çok Answers ile bir oluşturur . Questionen az bir tane olmalıdır Answer.

Açıklama: düşünün Questionve Answerbir testte olduğu gibi : bir soru var, ancak birkaç cevap var, azın doğru olabilir. Kullanıcı bu testi hazırlayan aktördür, bu nedenle soru ve cevaplar oluşturur.

Bu basit örneği modellemeye çalışıyorum, böylece 1) gerçek hayat modelini eşleştirin 2) kod ile ifade etmek, böylece olası yanlış kullanım ve hataları en aza indirmek ve geliştiricilere modelin nasıl kullanılacağına dair ipuçları vermek için.

Soru bir varlık , cevap ise değer nesnesidir . Soru cevapları tutar. Şimdiye kadar bu olası çözümlere sahibim.

[A] fabrika içindeQuestion

AnswerManuel olarak oluşturmak yerine şunları çağırabiliriz:

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

Yani bir cevap oluşturur ve bu soruya ekleyin. Sonra cevabı özelliklerini ayarlayarak değiştirebiliriz. Bu şekilde, sadece sorular bir cevabı kesebilir. Ayrıca, soru olmadan bir cevap almayı da engelliyoruz. Ancak, bunun kodlanması zor olduğu için cevaplar oluşturma üzerinde kontrolümüz yoktur Question.

Yukarıdaki kodun “dili” ile ilgili bir problem de vardır. Soru değil cevaplar yaratan kullanıcıdır. Şahsen, değer nesnesi oluşturmamızı sevmiyorum ve geliştiriciye değerlerle doldurmak için bağlıyım - ne eklemek gerektiğinden nasıl emin olabilirim?

[B] Fabrika içinde Soru, # 2 almak

Bazıları bu tür bir yöntem olması gerektiğini söylüyor Question:

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

Yukarıdaki çözüme benzer şekilde, bu yöntem cevap için zorunlu verileri alır ve soruya da eklenecek olanı oluşturur.

Burada sorun , yapıcısını iyi bir sebep olmadan çoğaltmamızdırAnswer . Ayrıca, soru gerçekten bir cevap yaratıyor mu?

[C] Yapıcı bağımlılıkları

Her iki nesneyi kendimiz tarafından yaratmakta özgür olalım. Yapıcıda bağımlılık hakkını da ifade edelim:

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

Bu, geliştiriciye ipuçları verir, çünkü cevap bir soru olmadan oluşturulamaz. Ancak, cevabın soruya 'eklendiğini söyleyen' dili 'görmüyoruz. Öte yandan, gerçekten görmemiz gerekiyor mu?

[D] Yapıcı bağımlılığı, # 2 al

Bunun tam tersini yapabiliriz:

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

Bu yukarıdakilerin tam tersi bir durumdur. Burada cevaplar bir soru olmadan (mantıklı değil) var olabilir, ancak soru cevap olmadan (mantıklı) var olamaz. Ayrıca, 'dil' burada olacağı soru üzerine daha açıktır sahip cevapları.

[E] Ortak yol

Ben ortak yolu, ppl genellikle yapmak ilk şey budur:

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

hem cevap hem de soru birbirleri olmadan var olabileceğinden, iki cevabın 'gevşek' versiyonudur. Bunu hiçbir özel ipucu yoktur sahip bunları birbirine bağlamak için.

[F] Kombine

Ya da C, D, E - 'yi bir araya getirerek ilişkinin nasıl yapılabileceğinin tüm yollarını kapsayacak şekilde birleştirmeliyim, böylece geliştiricilerin kendileri için en iyi olanı kullanmalarına yardımcı olmak için.

Soru

İnsanların yukarıdaki cevaplardan birini 'önseziye' göre seçebileceklerini biliyorum. Ama yukarıdaki varyantlardan herhangi birinin diğerinden daha iyi olup olmadığını merak ediyorum. Ayrıca, lütfen yukarıdaki sorunun içinde düşünmeyin, burada çoğu durumda uygulanabilecek bazı en iyi uygulamaları sıkmak istiyorum - ve kabul ederseniz, bazı varlıkların yaratılış vakalarının çoğu benzerdir. Ayrıca, burada teknoloji agnostik olalım, örneğin. ORM'in kullanılıp kullanılmayacağını düşünmek istemiyorum. Sadece iyi, etkileyici mod istiyorum.

Bunun hakkında bilgelik var mı?

DÜZENLE

Lütfen sorusunun diğer özelliklerini göz ardı edin Questionve Answerbunlar ilgili değildir. Yukarıdaki metni düzenledim ve (gerektiğinde) kurucuların çoğunu değiştirdim: şimdi gerekli özellik değerlerinden herhangi birini kabul ediyorlar. Bu sadece bir soru dizesi veya farklı dillerdeki, durumlardaki vb. Dizelerin haritası olabilir - hangi özellikler geçilirse geçirilsin, bunlar için bir odak noktası değildir;) Öyleyse, aksi belirtilmedikçe, gerekli parametreleri geçmenin üstünde olduğumuzu varsayalım. Thanx!

Yanıtlar:


6

Güncellenmiş. Açıklamalar dikkate alınmıştır.

Bu, genellikle aşağıdaki gereksinimlere sahip çoktan seçmeli bir alan adı gibi görünüyor

  1. bir sorunun en az iki seçeneği olması gerekir;
  2. en az bir doğru seçim olmalı
  3. soru olmadan bir seçim olmamalı

Yukarıdakilere dayanarak

[A] değişmezi 1. noktadan temin edemez, herhangi bir seçim yapmadan bir soru sorabilirsiniz

[B] , [A] ile aynı dezavantaja sahiptir

[C] , [A] ve [B] ile aynı dezavantaja sahiptir

[D] geçerli bir yaklaşımdır, ancak seçimleri tek tek geçmek yerine liste olarak geçirmek daha iyidir

[E] , [A] , [B] ve [C] ile aynı dezavantaja sahiptir

Bu nedenle, [D] 'ye giderim çünkü 1, 2 ve 3. noktalardan etki alanı kurallarının takip edilmesini sağlar. Bir sorunun uzun bir süre boyunca herhangi bir seçim yapmadan kalmasının pek mümkün olmadığını söyleseniz bile, alan adı gereksinimlerini kod yoluyla iletmek her zaman iyi bir fikirdir.

Ben de bu alanda benim için daha anlamlı olduğu Answeriçin Choiceolarak yeniden adlandırmak .

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);

Bir not. Eğer yaparsanız Questionişletmeye bırakmıştır toplu kök ve Choicedeğer nesnesi aynı agreganın bir parçası, tek bir saklayabilir hiçbir şansı yoktur Choice, bir tahsis edilmeden Questionsen doğrudan başvuru geçemiyor olsa bile ( Questionbir argüman olarak Choice'ın yapıcısı), çünkü depolar yalnızca köklerle çalışır ve bir kez oluşturduğunuzda Question, yapıcıda ona tüm seçenekleriniz atanır.

Bu yardımcı olur umarım.

GÜNCELLEME

Soruların önünde seçimlerin nasıl oluşturulduğunu gerçekten rahatsız ediyorsa, yararlı bulabileceğiniz birkaç püf noktası vardır.

1) Kodu, sorudan sonra veya en azından aynı anda oluşturulacak şekilde yeniden düzenleyin

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

2) Yapıcıları gizleyin ve statik fabrika yöntemi kullanın

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) Oluşturucu desenini kullanın

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

Ancak, her şey alan adınıza bağlıdır. Çoğu zaman, nesnelerin oluşturulma sırası problem etki alanı açısından önemli değildir. Daha da önemlisi, sınıfınızın bir örneğini alır almaz mantıksal olarak eksiksiz ve kullanıma hazır olmasıdır.


Demode. Aşağıdaki her şey, açıklamalardan sonra soru ile ilgisizdir.

Her şeyden önce, DDD alan modeline göre gerçek dünyada mantıklı olmalıdır. Bu nedenle, birkaç puan

  1. bir sorunun cevabı olmayabilir
  2. soru olmadan cevap olmamalı
  3. bir cevap tam olarak bir soruya karşılık gelmelidir
  4. "boş" cevap bir soruya cevap vermez

Yukarıdakilere dayanarak

[A] 4. maddeyle çelişebilir, çünkü metni yanlış kullanmak ve metni ayarlamayı unutmak kolaydır.

[B] geçerli bir yaklaşımdır ancak isteğe bağlı parametreler gerektirir

[C] 4. maddeyle çelişebilir, çünkü metin içermeyen bir cevaba izin verir

[D] 1. nokta ile çelişiyor ve 2. ve 3. noktalarla çelişiyor olabilir

[E] 2, 3 ve 4. noktalarla çelişebilir

İkinci olarak, etki alanı mantığını uygulamak için OOP özelliklerini kullanabiliriz. Yani gerekli parametreler için yapıcıları ve isteğe bağlı olanlar için ayarlayıcıları kullanabiliriz.

Üçüncüsü, alan adı için daha doğal olması gereken her yerde kullanılan dili kullanacağım.

Son olarak, hepsini toplu kökler, varlıklar ve değer nesneleri gibi DDD kalıpları kullanarak tasarlayabiliriz. Soruyu toplamının kökü ve Yanıtın bir parçası haline getirebiliriz. Bu mantıklı bir karardır çünkü cevabın bir sorunun bağlamı dışında bir anlamı yoktur.

Yani, yukarıdakilerin tümü aşağıdaki tasarıma kadar kaynar

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 Sorunuza cevap vererek, alan adınızla ilgili doğru olmayabilecek birkaç varsayım yaptım, bu yüzden yukarıdakileri ayrıntılarınızla ayarlamaktan çekinmeyin.


1
Özetlemek gerekirse: Bu, B ve C'nin bir karışımıdır. Lütfen gereksinimler hakkındaki açıklamalarıma bakın. 1. noktanız bir soru oluştururken sadece 'kısa' bir süre için var olabilir; ancak veritabanında değil. Bu anlamda 4. asla olmamalı. Umarım gereksinimler açıktır;)
lawpert

Btw, açıklama ile bana öyle geliyor addAnswerya assignAnswerda sadece daha iyi bir dil olurdu answer, umarım bu konuda hemfikirsiniz. Her neyse, sorum şu: B'ye devam edip örneğin cevap yöntemindeki argümanların çoğunun bir kopyasına sahip misiniz? Çoğaltma olmaz mıydı?
lawpert

Belirsiz gereksinimler için özür dilerim, cevabı güncellemek için çok nazik misiniz?
lawpert

1
Varsayımlarımın yanlış olduğu ortaya çıktı. KG alan adınızı stackexchange web sitelerine örnek olarak gördüm, ancak çoktan seçmeli bir teste benziyor. Tabii, cevabımı güncelleyeceğim.
zafarkhaja

1
@lawpert Answerbir değer nesnesidir, toplamının bir köküyle depolanacaktır. Değer nesnelerini doğrudan depolamazsınız veya varlıkları kümelerinin kökleri değilse kaydedemezsiniz.
zafarkhaja

1

Gereksinimlerin bu kadar basit olması durumunda, birden fazla olası çözüm mevcutsa, KISS ilkesine uyulmalıdır. Sizin durumunuzda, bu E seçeneği olacaktır.

Ayrıca, bir şeyi ifade eden, olmaması gereken kod oluşturma durumu da vardır. Örneğin, Soruya Yanıtlar (A ve B) oluşturulmasını bağlama veya Soru (C ve D) 'ye Yanıt referansı verme, etki alanı için gerekli olmayan ve kafa karıştırıcı olabilecek bazı davranışlar ekler. Ayrıca, sizin durumunuzda, Soru büyük olasılıkla Cevapla toplanır ve Yanıt bir değer türü olur.


1
[C] neden gereksiz bir davranıştır? Gördüğüm gibi, [C] Cevap'ın bir Soru olmadan yaşayamayacağını ve tam da bu olduğunu söyler. Ayrıca, Yanıt'ın zorunlu olan daha fazla bayrak gerektirdiğini (örneğin, yanıt türü, kategori, vb.) Düşünün. KISS'e gidiyoruz, neyin zorunlu olduğuna dair bilgiyi kaybediyoruz ve geliştirici, doğru yapmak için Cevaba ne eklemesi / ayarlaması gerektiğini önceden bilmelidir . Burada sorunun çok basit bir örneği modellemek değil, aynı zamanda OO kullanarak her yerde kullanılan dili yazmak için daha iyi bir uygulama bulmak olduğuna inanıyorum.
igor

@igor E, Yanıtın Sorunun bir parçası olduğunu, kaydedilmesi için soruya Yanıt atamasını zorunlu kılarak bildirir. Eğer soruyu yüklemeden sadece cevabı kaydetmenin bir yolu olsaydı, o zaman C daha iyi olurdu. Ama bu yazdıklarından belli değil.
Euphoric

@igor Ayrıca, Soru ile Yanıt oluşturmayı bağlamak istiyorsanız, A daha iyi olacaktır, çünkü C ile giderseniz, sorunun soruya atandığı zaman gizlenir. Ayrıca, metninizi A'da okurken, "model davranışı" ve bu davranışı kimin başlattığını ayırt etmelisiniz. Soru, cevabı bir şekilde başlatması gerektiğinde cevaplar oluşturmaktan sorumlu olabilir. Bunun "kullanıcı cevaplar oluşturması" ile ilgisi yoktur.
Euphoric

Sadece kayıt için, C&E arasında paramparça oldum :) Şimdi, bu: "... kaydedilmesi için soruya cevap vermeyi zorunlu hale getirerek depo." Bu, 'zorunlu' kısmın sadece depoya geldiğimizde geldiği anlamına gelir . Bu nedenle zorunlu bağlantı, derleme zamanında geliştirici tarafından 'görünür' değildir ve iş kuralları depoda sızıntı yapar. Bu yüzden burada [C] yi test ediyorum. Belki bu konuşma C seçeneğinin ne olduğunu düşündüğüm hakkında daha fazla bilgi verebilir.
igor

Bu: "... Soru ile Cevap oluşturmayı bağlamak istiyorum ...". _Creation'ın kendisini bağlamak istemiyorum . Sadece zorunlu ilişkiyi ifade etmek istiyorum . (Kişisel olarak mümkünse kendim model nesneler oluşturmak istiyorum). Benim görüşüme göre, bu yaratmakla ilgili değil, bu yüzden yakında A ve B'yi terk ediyorum. Sorunun cevabı oluşturmaktan sorumlu olduğunu görmüyorum.
igor

1

[C] veya [E] ye giderdim.

İlk olarak, neden A ve B olmasın? Sorumun ilgili herhangi bir değer yaratmaktan sorumlu olmasını istemiyorum. Sorunun başka birçok değer nesnesi olup olmadığını düşünün - createher biri için yöntem koyardınız mı? Ya da bazı karmaşık agregalar varsa, aynı durum.

Neden [D] değil? Çünkü doğada sahip olduğumuz şeyin tersidir. Önce bir Soru yaratırız. Tüm bunları oluşturduğunuz bir web sayfası hayal edebilirsiniz - kullanıcı önce bir soru oluşturur, değil mi? Bu nedenle, D değil.

@Euphoric'in dediği gibi [E] KISS. Ama son zamanlarda da [C] 'yi sevmeye başladım. Bu göründüğü kadar kafa karıştırıcı değil. Dahası, Sorunun daha fazla şeye bağlı olup olmadığını düşünün - o zaman geliştirici , Sorunun içine doğru bir şekilde başlatılması için ne koyması gerektiğini bilmelidir . Haklı olmanıza rağmen - soruya cevabın gerçekten eklendiğini açıklayan 'görsel' bir dil yoktur.

Ek okuma

Bunun gibi sorular bilgisayar dillerimizin modelleme için çok genel olup olmadığını merak etmemi sağlıyor. ( Tüm programlama gereksinimlerini karşılamak için genel olmaları gerektiğini anlıyorum ). Son zamanlarda akıcı arayüzler kullanarak iş dilini ifade etmenin daha iyi bir yolunu bulmaya çalışıyorum. Bunun gibi bir şey (sudo dilinde):

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

Yani herhangi bir büyük * Hizmetler ve * Depo sınıflarından daha küçük iş mantığı yığınlarına geçmeye çalışmak. Sadece bir fikir.


Eklentide Etki Alanına Özel Diller hakkında mı konuşuyorsunuz?
lawpert

Şimdi bahsettiğinizde, öyle görünüyor :) Buy Onunla önemli bir deneyimim yok.
igor

2
Şimdiye kadar IO'nun dikey bir sorumluluk olduğu ve bu nedenle varlıklar tarafından ele alınmaması gerektiğine dair bir fikir birliği olduğunu düşünüyorum (storeToRepo)
Esben Skov Pedersen

@Esben Skov Pedersen'in, kurumun kendi içinde repo dememesi gerektiğini kabul ediyorum (söylediklerin bu, değil mi?); ancak burada AFAIU olarak, arkasında komutları çağıran bir çeşit oluşturucu paternimiz var; bu yüzden IO burada varlıkta yapılmaz. En azından ben böyle anladım;)
lawpert

@lawpert doğru. Nasıl çalışması gerektiğini göremiyorum ama ilginç olurdu.
Esben Skov Pedersen

1

Burada bir noktayı kaçırdığınıza inanıyorum, Toplam kökünüz Test Varlığınız olmalıdır.

Ve gerçekten durum buysa, bir TestFactory'nin sorununuzu cevaplamak için en uygun olacağına inanıyorum.

Soru ve Cevap binasını Fabrikaya devredersiniz ve bu nedenle temelde modelinizi bozmadan düşündüğünüz herhangi bir çözümü kullanabilirsiniz, çünkü müşteriye alt varlıklarınızı somutlaştırdığınız şekilde saklıyorsunuz.

TestFactory, Testinizi örneklemek için kullandığınız tek arayüz olduğu sürece budur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.