基于类型添加行为的最佳方式

时间:2014-08-12 16:14:06

标签: c# class design-patterns entity

我有一个公司实体

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
        }
         ..........
    }

说实话,我不喜欢这个。是否有任何设计模式可能有助于克服这一场景?你会做什么以及为什么设计这个场景?

3 个答案:

答案 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)
   {...}
}

通过这种方式,您可以消除InvoiceGetComission等方法中各种类型的测试。

答案 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个子类!),代码中到处都是NotImplementedExceptionsInvalidOperationException。 / p>

5)你不必使用enum或&#34; Type&#34;领域,特别是当你要求混合和匹配这些能力时(你不仅需要一个枚举,而是一个标志枚举)。使用类型系统来表示不同的类型和行为,而不是枚举。

6)它干了。

关于这种方法的坏事:

1)显式接口实现和重写显式转换操作符并不完全是C#编码知识,可能会让那些追随你的人感到困惑。

编辑:

好吧,我没有测试这个想法就回答太快了,这对接口来说并不起作用。但是,请看另一个想法的另一个答案。

答案 2 :(得分:0)

与大多数DDD个问题一样,它通常归结为Bounded Contexts。我猜你在这里处理的是一些截然不同的背景(从你的陈述中可以看出这一点“公司可以是代理人或供应商,也可以是两者兼而有之。”)。在至少一个上下文中,您需要平等地考虑所有Company个实体,无论它们是代理还是供应商。但是,我认为您需要考虑您的InvoiceGetCommission操作是否适用于更广泛的背景?我会说这些将适用于更专业的背景,其中AgentSupplier之间的区别更为重要。

您可能遇到了麻烦,因为您正在尝试创建一个适用于所有上下文的全方位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的一个教训是我们将域分割成这些有界的上下文,并且不要试图制作可以做任何事情的“上帝”对象。