我有一个公司实体
public class Company : Entity<Company>
{
public CompanyIdentifier Id { get; private set; }
public string Name { get; private set; }
..............
..........
}
公司可以是代理商或供应商,也可以 或无。 (有更多类型)其行为应根据类型进行更改。代理商可以获得佣金,供应商可以开具发票。 设计实体或实体或价值对象的最佳方式是什么?我可以选择添加一些布尔类型并在方法中检查这些值,
public class Company : Entity<Company>
{
public CompanyIdentifier Id { get; private set; }
public string Name { get; private set; }
public bool IsAgent { get; private set; }
public bool IsSupplier { get; private set; }
..........
public void Invoice()
{
if(!IsSupplier)
{
throw exception.....;
}
//do something
}
public void GetCommission(int month)
{
if(!IsAgent)
{
throw exception.....;
}
//do something
}
..........
}
说实话,我不喜欢这个。是否有任何设计模式可能有助于克服这一场景?你会做什么以及为什么设计这个场景?
答案 0 :(得分:1)
我会考虑将不同类中所有类型的实现分开。您可以通过使用枚举来表示公司类型来开始这样做。
public enum CompanyType
{
Agent = 0,
Supplier
}
public abstract class Company : Entity<Company>
{
public CompanyIdentifier Id { get; private set; }
public string Name { get; private set; }
public CompanyType EntityType { get; private set; }
public abstract void Invoice();
public abstract void GetCommission(int month);
...
这样你获得的公共财产就会减少。
接下来,我将为供应商和代理商实施专门的类(然后是两者都没有)。你可以使Company
抽象,任何专门的方法都是抽象的。
这将允许您分离每种类型实体的不同行为。当你回到它进行维护时派上用场。它还使代码更容易阅读/理解。
public class SupplierCompany : Company
{
public SupplierCompany()
{
EntityType = CompanyType.Supplier;
}
public override void Invoice()
{...}
public override void GetComission(int month)
{...}
}
public class AgentCompany : Company
{
public AgentCompany()
{
EntityType = EntityType.Agent;
}
public override void Invoice()
{...}
public override void GetComission(int month)
{...}
}
通过这种方式,您可以消除Invoice
和GetComission
等方法中各种类型的测试。
答案 1 :(得分:1)
显式实现接口,然后覆盖强制转换运算符,使其仅在有效时转换为该接口。
public class Company : ...., IAgentCompany, ISupplierCompany ... {
public double IAgentCompany.GetCommission(int month) {
/*do stuff */
}
public static explicit operator IAgentCompany(Company c) {
if(!c.IsAgent)
throw new InvalidOperationException();
return this;
}
}
必须通过接口调用接口的显式实现,而不是具体类型:
// Will not compile
new Company().GetCommission(5);
// Will compile
((IAgentCompany)new Company()).GetCommission(5)
但是,现在我们已经重载了显式的强制转换操作符。那是什么意思呢?我们无法在不转发给IAgentCompany的情况下调用GetCommission,现在我们有一个警卫来防止那些没有被标记为代理的公司的演员表。
这种方法的好处:
1)您有接口可以定义不同类型公司的方面以及它们可以做什么。界面隔离是一件好事,并使每种类型的公司的能力/责任都清晰。
2)您已经取消了对您要呼叫的每个功能的检查,而不是&#34;全球&#34;对所有公司。你在演员时进行一次检查,然后只要你在一个键入的变量中作为界面,你就可以愉快地与它进行交互而无需进一步检查。这意味着可以减少引入错误的地方,减少无用的检查。
3)您正在利用语言功能,并利用类型系统来帮助使代码更具防弹性。
4)你不必编写大量的子类来实现代码的各种接口组合(可能是2 ^ n个子类!),代码中到处都是NotImplementedExceptions
或InvalidOperationException
。 / p>
5)你不必使用enum或&#34; Type&#34;领域,特别是当你要求混合和匹配这些能力时(你不仅需要一个枚举,而是一个标志枚举)。使用类型系统来表示不同的类型和行为,而不是枚举。
6)它干了。
关于这种方法的坏事:
1)显式接口实现和重写显式转换操作符并不完全是C#编码知识,可能会让那些追随你的人感到困惑。
编辑:
好吧,我没有测试这个想法就回答太快了,这对接口来说并不起作用。但是,请看另一个想法的另一个答案。
答案 2 :(得分:0)
与大多数DDD
个问题一样,它通常归结为Bounded Contexts
。我猜你在这里处理的是一些截然不同的背景(从你的陈述中可以看出这一点“公司可以是代理人或供应商,也可以是两者兼而有之。”)。在至少一个上下文中,您需要平等地考虑所有Company
个实体,无论它们是代理还是供应商。但是,我认为您需要考虑您的Invoice
或GetCommission
操作是否适用于更广泛的背景?我会说这些将适用于更专业的背景,其中Agent
和Supplier
之间的区别更为重要。
您可能遇到了麻烦,因为您正在尝试创建一个适用于所有上下文的全方位Company
实体...如果没有奇怪的代码,这几乎是不可能实现的构造与与类型系统作斗争(正如你在其他答案中所建议的那样)。
请阅读http://martinfowler.com/bliki/BoundedContext.html
粗略了解您的情境可能如何:
Broad "Company" Context
{
Entity Company
{
ID : CompanyIdentifier
Name : String
}
}
Specialized "Procurement" Context
{
Entity Supplier
{
ID : CompanyIdentifier
Name : String
Invoice()
}
}
Specialized "Sales" Context
{
Entity Agent
{
ID : CompanyIdentifier
Name : String
GetComission()
}
}
在采购和销售环境中尝试使用相同的对象是否有意义?毕竟,这些背景有着非常不同的要求。 DDD的一个教训是我们将域分割成这些有界的上下文,并且不要试图制作可以做任何事情的“上帝”对象。