假设我有一个房地产网站,可以让您向不同的房地产经纪人查询给定的房产。不同的查询方法可能有不同的计费计算与之关联,并不是所有的代理都启用了每种计费模型。
public class EmailEnquiryBillingModel : ValueObject
{
public string EmailAddress { get; set; }
public decimal CostPerEnquiry { get; set; }
}
public enum DayOfWeek
{
Monday,
Tuseday,
// etc.
}
public class OpeningHours : ValueObject
{
public DateTime OpeningTime { get; set; }
public DateTime ClosingTime { get; set;}
}
public class PhoneEnquiryBillingModel : ValueObject
{
public PhoneEnquiryBillingModel()
{
OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
}
public int PhoneNumber { get; set; }
public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }
}
public class EstateAgent : Entity
{
public string Name { get; set; }
public EmailEnquiryBillingModel EmailEnquiryBillingModel { get; set; }
public PhoneEnquiryBillingModel PhoneEnquiryBillingModel { get; set; }
}
NHibernate有组件(值对象)的语义,如果组件中的每个属性都为空,那么该组件也将为空。
因此,通过适当的映射,您可以编写if(estateAgent.EmailEnquiryBillingModel != null)
,而不必检查电子邮件查询计费模型的每个单独属性,或者模型是否有效:我们要么有模型,要么没有。这是一种检查是否启用了特定计费模型的简单而优雅的方法。
问题是当你有一组在一个组件中,如与电话查询计费模型和各种开放时间。PhoneEnquiryBillingModel
和OpeningHours
都不是实体。这些都是合法的有价值的对象:我们不关心房地产代理是在这个星期一上午9点还是那个星期一上午9点开放,只关心它在星期一上午9点开放。
因此,这感觉像是在c#中表示这个领域模型的语义正确的方式。
然而,PhoneEnquiryBillingModel
包含一个集合(ProviderOpenHours
),并且一个集合在NHibernate中不能为空,只能为空,这意味着ProviderOpenHours
将始终是非空的,即使房地产代理没有有意意义地启用该查询模型。(有关更多信息,请参阅:https://ayende.com/blog/4685/those-are-the-rules-even-when-you-dont-like-them)。
这意味着你不能像if(estateAgent.PhoneEnquiryBillingModel != null)
那样做一个简单的检查,因为那个对象是总是not-null。
因此,对于某些计费模型,您可以执行null检查以查看它们是否启用,但对于其他计费模型,您必须找到另一种检查方法,这取决于这些计费模型是否包含集合。
实际上,您需要知道计费模型的内部结构,以知道您是否可以进行这种比较,这感觉就像您正在打破封装并根据ORM规则更改域模型。
有更好的建模方法吗?或者有一种方法可以让NHibernate在PhoneEnquiryBillingModel
中序列化为null,如果它没有电话号码或任何开放时间?
因此,用适当的映射,您可以编写如果(estateAgent。EmailEnquiryBillingModel = null)
这本身并不是最好的封装。
你可以这样写:
if (estateAgent.DoesAcceptEmailEnquiries())
和
if (estateAgent.DoesAcceptPhoneEnquiries())
这将提供更好的封装,而不是询问EstageAgent聚合上的属性来对EstateAgent的功能进行假设。如果你决定改变的实现一个内部EstateAgent存储这些信息如何?您需要更改所有客户端。
没有什么特别不好EstateAgent执行个人财产检查它的潜在价值对象。
但是,您可以进一步在PhoneEnquiryBillingModel上实现一个检查器方法,甚至可以是静态的,以避免在EstateAgent中检查null。
PhoneEnquiryBillingModel
public class PhoneEnquiryBillingModel : ValueObject
{
public PhoneEnquiryBillingModel()
{
OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
}
public int PhoneNumber { get; set; }
public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }
public static bool DoesAcceptEnquiries(PhoneEnquiryBillingModel phone)
{
if (phone == null) return false;
if (phone.OpeningHours.Count == 0) return false;
return true;
}
}
房地产经纪人
public class EstateAgent : Entity
{
public string Name { get; set; }
public EmailEnquiryBillingModel _emailEnquiryBillingModel { get; set; }
public PhoneEnquiryBillingModel _phoneEnquiryBillingModel { get; set; }
public bool DoesAcceptPhoneEnquiries()
{
return PhoneEnquiryBillingModel.DoesAcceptEnquiries(
_phoneEnquiryBillingModel);
}
}