我想知道是否有一种更优雅的方式来管理个人的联系方式。暂时忘记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。
答案 0 :(得分:5)
我们肯定知道什么是联系方式&#34;电子邮件,&#34;电话&#34;并且&#34;地址&#34;因此,确定我们首先要做的就是对这些概念进行建模,同时考虑它们的实际情况。我们采取电子邮件&#34;作为示例,看看它是什么,以便正确建模。它是一个值对象(一个不可变对象),一旦创建它就永远不会改变,因为整数也是一个不可变对象。不同之处在于,对于整数的建模,我们可以使用任何编程语言提供的int类型,但问题是我们用什么类来建模电子邮件?大多数人会使用String实例来建模电子邮件,但这样可以吗?为了回答这个问题,让我们看看String对象知道响应的协议(消息集)是什么:&#34; charAt(anIndex),replace(aString,anotherString)等...&# 34 ;.想象一下,如果我们使用String类对电子邮件建模,我们可以询问电子邮件&#34; replace(aString,anotherString)&#34;。这听起来很奇怪,该消息不应该是电子邮件应该向其他对象公开的行为的一部分。同样如此重要我们说电子邮件是不可变的,它不能暴露最终改变状态的行为。因此,我们需要创建一个全新的抽象来模拟电子邮件,它是什么?电子邮件课最终进来!!!我知道你建议了,但我只是想让你知道为什么我们需要创建一个Email类。 首先这是DDD(面向对象)所以FORGET避免了setter和getter。在您创建的电子邮件类中,您公开了一个setter方法,这意味着您可以更改电子邮件,这与电子邮件的性质(不可变)相矛盾。电子邮件从创建的那一刻起是不可变的:
Email.fromString("monicalewinsky@gmail.com");
与做
相同new Email("monicalewinsky@gmail.com");
fromString方法是一种工厂方法,它为我们的域模型添加了语义。这在smalltalk中很常见,而不是直接调用构造函数。我们完了吗???一点也不。应该创建一个电子邮件实例,只要它有效,这样电子邮件类就应该声明创建的字符串是有效的:
Email(String anEmailStringRepresentation) {
assertIsValid(anEmailStringRepresentation);
}
断言有效应该验证它实际上是一个电子邮件字符串表示。这只有一个@字符,其本地部分有效,然后其域部分有效。您可以查看维基百科的电子邮件地址,以更好地了解它的组成方式。 永远记住,编程是一个学习过程,只要我们更好地理解一个领域,我们就会更好地在代码中反映这个领域,并且它总是必须与现实世界保持一致!我们的电子邮件类应该或多或少像:
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)
}
那就是它。它与PhoneNumber相同。它也是一个不可改变的对象,您应该创建一个具有自己的协议的类。请记住,如果我们正在进行DDD,请不要使用set / get。我不认为您需要两个价值对象电话和移动,因为这些是多态对象,您可以使用TelephoneNumber抽象建模移动电话号码或家庭电话号码。这就像建模信用卡一样。最后,您将了解到,如使用Visa,万事达卡等等,可以使用CreditCard课程,并且设计更好。 让我们跳过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也是一个不可变类,实际上经验法则是聚合根应理想地只有一个值对象的聚合。 这最终是你的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));
}
}
答案 1 :(得分:0)
在我学习DDD的过程中,我有时会看到模式而不是问题...一个有趣的例子Everything seems to be an Aggregate Root是我提供的关于菜单的另一个答案,它有不同的类别,如入门,主菜,沙漠等。
我已将其隐式建模为类别字符串。在我发布后,有第二个答案,有人建议将这些建模为明确的列表:
Menu {
List<Food> starters;
List<Food> entrees;
List<Food> desserts;
List<Food> drinks;
}
通过这种方式,消除了食品类别的整个概念,这对我来说很有启发性,并且看到了不同的建模方式,在这种情况下降低了复杂性。
我的观点是尝试对代码进行建模,以便在我与业务专家(不是开发人员)坐下来向他们展示高级person.SetMobileNumber("078321411", Countries.UK)
的用例代码时,他们就可以理解它:
public void HandleUpdateMobileCommand(UpdateMobileNumber command)
{
// repositories, services etc are provided in this handler class constructor
var user = this.UserRepository.GetById(command.UserId);
user.SetMobile(command.number, command.country);
this.UserRepository.Save(user);
// send an SMS, this would get the number from user.Mobile
this.SmsService.SendThankYouMessage(user);
}
或者甚至更好,当您更新用户移动设备时,您可能会触发MobileUpdated
事件,其他地方的某些代码(发送短信的专家,而不是其他任何内容)正在侦听这些事件 - 对我而言,这是DDD将代码分解为专家系统的真正力量。
总而言之,我认为您使用Post
,Mobile
,Landline
和Email
明确建模的第二个建议最有意义。
我不会说这是一个DDD域,因为没有足够的信息来说明你需要的任何复杂逻辑(或多用户竞争条件),只是提到不要忘记你可能会更好写一个CRUD应用程序,如果在这种情况下更有意义。
答案 2 :(得分:0)
DDD的核心理念是,域建模必须通过与领域专家的讨论来形成。如果你是凭空捏造这些类名,很可能它们不会与你的真实领域完全匹配。电子邮件或电话等微不足道的应该是正确的,但对于其他人,您可能需要先咨询专家。
一般来说,确实优先考虑使用基本类型的专用值对象进行语义丰富的建模。在C#中,它需要付出代价,因为所需的样板代码量很大(例如,与F#不同)。这就是为什么我通常喜欢只在类型具有多个属性或者存在特定构造规则或不变量时才这样做。
答案 3 :(得分:0)
您可以做的一件好事是将您的类型建模为不可变Value Objects
。如下所示:
public class Telephone
{
public string AreaCode { get; set; }
public string TelephoneNo { get; set; }
}
可能会成为:
public class TelephoneNumber
{
private string areaCode;
private string subscriberNumber;
private TelephoneNumber()
{
}
public TelephoneNumber(string areaCode, string subscriberNumber)
{
this.AreaCode = areaCode;
this.SubscriberNumber = subscriberNumber;
}
public string AreaCode
{
get
{
return this.areaCode;
}
private set
{
if (value == null)
{
throw new ArgumentNullException("AreaCode");
}
if ((value.Length <= 0) || (value.Length > 5))
{
throw new ArgumentOutOfRangeException("AreaCode");
}
this.areaCode = value;
}
}
// Etc.
}