c# - 为个人/客户建模联系方式

标签 c# domain-driven-design software-design

我想知道是否有更优雅的方式来管理个人的联系方式。暂时忘记 SQL 方面的事情,我很想知道人们可能会如何尝试通过 DDD 方法来驱动它。

为了让 DDD 成为一个整体,我一直在玩弄一些代码,并想出了以下看起来很糟糕的代码。

首先,我有一个名为 Person 的对象(为了本文的目的而进行了简化),我设想在其中添加和从本质上管理与个人交流的不同方法的方法。

public class Person
{
    public Person()
    {
        this.ContactDetails = new List<ContactDetails>();
    }

    public void AssociateContactDetails(ContactDetails contactDetails)
    {
        var existingContactDetails = this.ContactDetails.FirstOrDefault(x => x.ContactType == contactDetails.ContactType);

        if (existingContactDetails != null)
        {
            this.ContactDetails.Remove(existingContactDetails);
        }

        this.ContactDetails.Add(contactDetails);
    }

    public IList<ContactDetails> ContactDetails { get; private set; }
}

我想到了两种方法。一个我有一个相当简单的对象,比如下面这个非常通用的对象(松散地使用这个术语)。
public enum ContactType
{
    Email, Telephone, Mobile, Post
}   

public class ContactDetails
{
    private readonly ContactType contactType;
    private readonly string value;

    public ContactDetails(ContactType contactType, string value)
    {
        this.contactType = contactType;
        this.value = value;
    }

    public ContactType ContactType
    {
        get { return this.contactType; }
    }

    public string Value
    {
        get { return this.value; }
    }
}   

但是后来我用这种方法让自己陷入了困境,因为虽然它适用于诸如电子邮件和电话之类的琐碎项目,但当涉及到诸如邮政之类的东西时,字符串并不能完全解决它。因此,在此之后,我将走向让每种通信机制都由其自己的类型表示的方法,即:
public class Post
{
    public Address PostalAddress { get; set; }
}

public class Mobile
{
    public string MobileNo { get; set; }
}

public class Telephone
{
    public string AreaCode { get; set; }

    public string TelephoneNo { get; set; }
}

public class Email
{
    public string EmailAddress { get; set; }
}

那么每种类型都可以表示为 Person 类中的一个集合或单个实例吗?似乎冗长但可能更具可读性和可维护性。

我想的问题是,是否有一种更优雅的方式来实现这样的功能,以及是否有人可以为我指出一个与此类似的好例子的方向。我想这是一个需要克服的常见事情/问题。

干杯,DS。

最佳答案

我们肯定知道什么是联系方式“email”、“phone”和“address”,所以在确定了这些之后,我们首先要做的是对这些概念进行建模,同时考虑到它们的真实含义。让我们以“email”为例看看它到底是什么以便对其进行正确建模。它是一个值对象(一个不可变对象(immutable对象)),一旦创建它就永远不会改变,就像整数也是一个不可变对象(immutable对象)一样。不同之处在于对整数建模number 我们可以使用任何编程语言提供的 int 类型,但问题是我们使用什么类来建模 en Email?大多数人会使用 String 实例来建模 Email,但是这样可以吗?为了回答它让我们看看 String 对象知道响应的协议(protocol)(消息集)是什么:“charAt(anIndex), replace(aString, anotherString), etc...”。想象一下,如果我们使用 String 类对电子邮件进行建模我们可以询问电子邮件“replace(aString, anotherString)”。这听起来很奇怪,那是一团糟年龄不应该是电子邮件应该暴露给其他对象的行为的一部分。同样重要的是,我们说电子邮件是不可变的,它不能暴露最终改变其状态的行为。所以很明显我们需要创建一个全新的抽象来建模电子邮件,它是什么?电子邮件类(class)终于来了!!!我知道您提出了建议,但我只是想让您了解为什么我们需要创建一个电子邮件类。
首先这是 DDD(面向对象),所以 FORGET 避免使用 setter 和 getter。在您创建的电子邮件类中,您公开了一个 setter 方法,这意味着您可以更改电子邮件,这与电子邮件的本质(不可变)相矛盾。电子邮件从创建的那一刻起就是不可变的:

Email.fromString("monicalewinsky@gmail.com");

这和做的一样
new Email("monicalewinsky@gmail.com");

fromString 方法是为我们的领域模型添加语义的工厂方法。这在 smalltalk 中很常见,而不是直接调用构造函数。我们完了吗???一点也不。只要电子邮件实例有效,就应该创建一个电子邮件实例,因此电子邮件类应该断言从中创建的字符串是有效的:
Email(String anEmailStringRepresentation) {
    assertIsValid(anEmailStringRepresentation);
}

assert 是有效的,应该验证它实际上是一个电子邮件字符串表示。这就是只有一个@字符,它的本地部分是有效的,然后它的域部分是有效的。您可以查看维基百科的电子邮件地址,以更好地了解它是如何组成的。
永远记住,编程是一个学习过程,只要我们越来越了解一个领域,我们就会在代码中反射(reflect)这个领域,并且它必须始终与现实世界保持一致!我们的 Email 类应该或多或少像:
class Email {

    String value;

    Email(aString) {
        value = aString;
 }

 public String getLocalPart()

 public String getDomainPart()

 public String asString()

 public boolean equals(anObject)

 public static Email fromString(aString)
}

而已。电话号码也是如此。它也是一个不可变对象(immutable对象),您应该创建一个具有自己协议(protocol)的类。记住,如果我们在做 DDD,千万不要像你出现的那样使用 set/get。我认为您不需要两个值对象 Telephone 和 Mobile,因为它们是多态对象,您可以使用 TelephoneNumber 抽象来建模手机号码或家庭电话号码。这就像为信用卡建模。最后你会明白,类 CreditCard 就足够了,并且比拥有几个类(如 Visa、MasterCard 等)更好。
让我们跳过 Address 类,让我们现在回到您的问题。
到目前为止,我们已经正确识别并创建了我们需要的所有值对象。现在我们需要创建一个抽象来表示电子邮件、电话号码、地址作为联系方式,如果我们保持对领域语言的忠诚,我们可以说:
ContactMethod.for(Email.fromString("monica@gmail.com"));

或者
ContactMethod.for(PhoneNumber("34234234234"));

等等

所以我们的 ContactMethod 看起来像:
class ContactMethod {

 static EMAIL = 1;
 static PHONE_TYPE = 2;
 static ADDRESS_TYPE = 3;

 String type;

 String value;

 ContactMethod(int aType, String aValue) {
     type = aType;
     value = aValue;
 }

 String getType()

 String getValue()

 public static ContactMethod at(Email anEmail) {
     return new ContactMethod(EMAIL, anEmail.asString());
 }

 public static ContactMethod at(PhoneNumber aPhoneNumber) {
     return new ContactMethod(PHONE_TYPE, aPhoneNumber.asString());
 }

 public static ContactMethod at(Address anAddress) {
     return new ContactMethod(ADDRESS_TYPE, anAddress.asString());
 }
}

看到 ContactMethod 也是一个不可变的类,实际上经验法则是 Aggregate 根在理想情况下应该只有值对象的聚合。
这最终是您的 Person 类的样子:
class Person {

    List<ContactMethod> contactMethods;

    contactedAt(Email anEmail) {
        contactMethods.add(ContactMethod.at(anEmail));
    }

    contactedAt(PhoneNumber aPhoneNumber) {
        contactMethods.add(ContactMethod.at(aPhoneNumber));
    }

    contactedAt(Address anAddress) {
        contactMethods.add(ContactMethod.at(anAddress));
    }
}

关于c# - 为个人/客户建模联系方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26975901/

相关文章:

domain-driven-design - 这是 DDD 应用程序还是域服务?

c# - 领域驱动设计中层与层之间的数据传输对象

java - 平衡设计原则 : Unit Testing

c# - 尝试将参数传递给 t4 模板时出错

c# - 将 ListView 与对象集合相关联

c# - 如何使用 CSV 文件中的数据运行 XUnit 测试

events - DDD 使用 NoSQL 处理限界上下文中多个聚合的最终一致性

php - DDD - 如何处理应用层中的获取或创建逻辑?

java - 构造函数最佳实践

c# - 如何在标签上表示二进制数字?