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

本文关键字:最佳 方式 添加行 于类型 类型 | 更新日期: 2023-09-27 18:17:52

我有一个公司实体

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
        }
         ..........
    }
老实说,我不喜欢这样。有没有什么设计模式可以帮助克服这种情况?您将做什么?为什么要设计这个场景?

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

显式实现接口,然后重写强制转换操作符,仅在有效时强制转换到该接口。

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)您已经消除了对您想要调用的每个不是所有公司"全局"的函数的检查。在强制转换时进行一次检查,然后只要将它作为接口类型放在变量中,就可以愉快地与它交互,而无需进一步检查。这意味着引入bug的地方更少,无用的检查也更少。

3)您正在利用语言的特性,并利用类型系统来帮助使代码更加防弹。

4)你不必编写大量的子类来实现各种接口的组合(可能是2^n个子类!),在你的代码中到处都是NotImplementedExceptionsInvalidOperationException

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

6)它是DRY.

这种方法的缺点:

1)显式接口实现和覆盖显式强制转换操作符并不是基本的c#编码知识,可能会让你的后继者感到困惑。

编辑:

嗯,我在没有测试这个想法的情况下回答得太快了,这对接口不起作用。

我会考虑在不同的类中分离所有这些类型的实现。您可以通过使用enum来表示公司类型来开始此操作。

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等方法中消除对各种类型的测试。

与大多数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的一个教训是,我们将领域划分为这些有界的上下文,并且不试图创建可以做所有事情的"上帝"对象。