c# - 重构代码以避免类型转换

标签 c# generics design-patterns domain-driven-design visitor-pattern

我在 .Net 4.0 中有以下 C# 代码。它需要将 IBusiness 类型转换为 IRetailBusiness。

//Type checking
if (bus is IRetailBusiness)
{
       //Type casting
       investmentReturns.Add(new RetailInvestmentReturn((IRetailBusiness)bus));
}

if (bus is IIntellectualRights)
{
       investmentReturns.Add(new IntellectualRightsInvestmentReturn((IIntellectualRights)bus));
}

业务场景:

我正在为 Investment Holding Company 设计一个软件系统。公司拥有零售业务和知识产权业务。 BookShop 和 AudioCDShop 是零售业务的示例。 EngineDesignPatent 和 BenzolMedicinePatent 是知识产权业务的例子。这两种业务类型完全无关。

投资公司有一个概念叫做InvestmentReturn(但是各个个体企业对这个概念一无所知)。 InvestmentReturn 是从每个业务中获得的利润,它是使用 ProfitElement 计算的。对于每个“业务类型”(零售、知识产权),使用的 ProfitElement 是不同的。

问题

如何重构此类设计以避免这种类型转换类型检查

抽象投资

public abstract class InvestmentReturn
{
    public double ProfitElement { get; set; }
    public IBusiness Business{ get;  set; }

    public abstract double GetInvestmentProfit();

    public double CalculateBaseProfit()
    {
       double profit = 0;

       if (ProfitElement < 5)
       {
           profit = ProfitElement * 5 / 100;
       }
       else if (ProfitElement < 20)
       {
           profit = ProfitElement * 7 / 100;
       }
       else
       {
           profit = ProfitElement * 10 / 100;
       }

       return profit;
    }
}

扩展

public class RetailInvestmentReturn : InvestmentReturn
{
    public RetailInvestmentReturn(IRetailBusiness retail)
    {
        Business = retail;
    }

    public override  double GetInvestmentProfit()
    {
        //GrossRevenue is the ProfitElement for RetailBusiness
        ProfitElement = ((IRetailBusiness)Business).GrossRevenue;
        return base.CalculateBaseProfit();
    }  
}

public class IntellectualRightsInvestmentReturn : InvestmentReturn
{

    public IntellectualRightsInvestmentReturn(IIntellectualRights intellectual)
    {
        Business = intellectual;
    }

    public override double GetInvestmentProfit()
    {
        //Royalty is the ProfitElement for IntellectualRights Business
        ProfitElement = ((IIntellectualRights)Business).Royalty;
        return base.CalculateBaseProfit();
    }
}

客户端

class Program
{

    static void Main(string[] args)
    {

        #region MyBusines

        List<IBusiness> allMyProfitableBusiness = new List<IBusiness>();

        BookShop bookShop1 = new BookShop(75);
        AudioCDShop cd1Shop = new AudioCDShop(80);
        EngineDesignPatent enginePatent = new EngineDesignPatent(1200);
        BenzolMedicinePatent medicinePatent = new BenzolMedicinePatent(1450);

        allMyProfitableBusiness.Add(bookShop1);
        allMyProfitableBusiness.Add(cd1Shop);
        allMyProfitableBusiness.Add(enginePatent);
        allMyProfitableBusiness.Add(medicinePatent);

        #endregion

        List<InvestmentReturn> investmentReturns = new List<InvestmentReturn>();

        foreach (IBusiness bus in allMyProfitableBusiness)
        {
            //Type checking
            if (bus is IRetailBusiness)
            {
                //Type casting
                investmentReturns.Add(new RetailInvestmentReturn((IRetailBusiness)bus));
            }

            if (bus is IIntellectualRights)
            {
                investmentReturns.Add(new IntellectualRightsInvestmentReturn((IIntellectualRights)bus));
            }
        }

        double totalProfit = 0;
        foreach (var profitelement in investmentReturns)
        {
            totalProfit = totalProfit + profitelement.GetInvestmentProfit();
            Console.WriteLine("Profit: {0:c}", profitelement.GetInvestmentProfit());
        }

        Console.ReadKey();
    }
}

业务领域实体

public interface IBusiness
{

}

public abstract class EntityBaseClass
{

}

public interface IRetailBusiness : IBusiness
{
    double GrossRevenue { get; set; }
}

public interface IIntellectualRights : IBusiness
{
    double Royalty { get; set; }
}



#region Intellectuals
public class EngineDesignPatent : EntityBaseClass, IIntellectualRights
{
    public double Royalty { get; set; }
    public EngineDesignPatent(double royalty)
    {
        Royalty = royalty;
    }
}

public class BenzolMedicinePatent : EntityBaseClass, IIntellectualRights
{
    public double Royalty { get; set; }
    public BenzolMedicinePatent(double royalty)
    {
        Royalty = royalty;
    }
}
#endregion

#region Retails
public class BookShop : EntityBaseClass, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public BookShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }
}

public class AudioCDShop : EntityBaseClass, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public AudioCDShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }
}
#endregion

引用文献

  1. Refactor my code : Avoiding casting in derived class
  2. Cast to generic type in C#
  3. How a Visitor implementation can handle unknown nodes
  4. Open Closed Principle and Visitor pattern implementation in C#

最佳答案

此解决方案使用业务接口(interface)知道它们必须创建返回并且它们的具体实现知道要创建什么种类具体返回的概念。

第一步InvestmentReturn拆分为两个接口(interface);原始减去 Business 属性和一个新的通用子类:

public abstract class InvestmentReturn
{
    public double ProfitElement { get; set; }
    public abstract double GetInvestmentProfit();

    public double CalculateBaseProfit()
    {
        // ...
    }
}

public abstract class InvestmentReturn<T>: InvestmentReturn where T : IBusiness
{
    public T Business { get; set; }        
}

第 2 步 从通用的继承,这样您就可以使用 Business 而无需转换:

public class RetailInvestmentReturn : InvestmentReturn<IRetailBusiness>
{
    // this won't compile; see **Variation** below for resolution to this problem...
    public RetailInvestmentReturn(IRetailBusiness retail)
    {
        Business = retail;
    }

    public override double GetInvestmentProfit()
    {
        ProfitElement = Business.GrossRevenue;
        return CalculateBaseProfit();
    }
}

第 3 步IBusiness 添加一个返回 InvestmentReturn 的方法:

public interface IBusiness
{
    InvestmentReturn GetReturn();
}

第 4 步 引入 EntityBaseClass 的通用子类,以提供上述方法的默认实现。如果你不这样做,你将不得不为所有的企业实现它。如果您这样做 这样做,则意味着您不想重复 GetReturn() 实现的所有类都必须从下面的类继承,这反过来意味着它们必须继承自 EntityBaseClass

public abstract class BusinessBaseClass<T> : EntityBaseClass, IBusiness where T : InvestmentReturn, new()
{
    public virtual InvestmentReturn GetReturn()
    {
        return new T();
    }
}

第 5 步如有必要为您的每个子类实现该方法。以下是 BookShop 的示例:

public class BookShop : BusinessBaseClass<RetailInvestment>, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public BookShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }

    // commented because not inheriting from EntityBaseClass directly
    // public InvestmentReturn GetReturn()
    // {
    //     return new RetailInvestmentReturn(this);
    // }
}

第 6 步 修改您的 Main 以添加 InvestmentReturn 的实例。您不必进行类型转换或类型检查,因为之前已经以类型安全的方式完成了这些操作:

    static void Main(string[] args)
    {
        var allMyProfitableBusiness = new List<IBusiness>();
        // ...
        var investmentReturns = allMyProfitableBusiness.Select(bus => bus.GetReturn()).ToList();
        // ...
    }

如果您不想让您的具体企业知道任何关于创建InvestmentReturn的信息——只知道他们必须在被问及时创建一个——那么您可能想要修改此模式以合并一个在给定输入的情况下创建返回的工厂(例如,IBusiness 实现和 InvestmentReturn 子类型之间的映射)。

变化

以上所有工作正常,如果您删除设置 Business 属性的投资返回构造函数,它们将编译。这样做意味着在别处设置 Business。这可能是不可取的。

另一种方法是在 GetReturn 中设置 Business 属性。我找到了一种方法来做到这一点,但它确实开始让类(class)看起来很乱。在这里供您评估是否值得。

RetailInvestmentReturn 中移除非默认构造函数:

public class RetailInvestmentReturn : InvestmentReturn<IRetailBusiness>
{
   public override double GetInvestmentProfit()
   {
       ProfitElement = Business.GrossRevenue;
       return CalculateBaseProfit();
   }
}

更改 BusinessBaseClass。这就是双重 Actor 变得困惑的地方,但至少它仅限于一个地方。

public abstract class BusinessBaseClass<T, U> : EntityBaseClass, IBusiness
    where T : InvestmentReturn<U>, new()
    where U : IBusiness
{
    public double GrossRevenue { get; set; }

    public virtual InvestmentReturn GetReturn()
    {
        return new T { Business = (U)(object)this };
    }
}

最终改变您的业务。以下是 BookShop 的示例:

public class BookShop : BusinessBaseClass<RetailInvestmentReturn, IRetailBusiness>, IRetailBusiness
{
    // ...
}

关于c# - 重构代码以避免类型转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21482850/

相关文章:

c# - sqlite-net的通用方法

C# 泛型和类型检查混淆

cocoa - Apple Cocoa 框架的设计模式 : MVC, MVP、Passive View...Apple 将走向何方?

java - 设计音频架构

c# - html敏捷得不到结果

c# - 将动态数据绑定(bind)到 rowdetails 模板内的 wpf datagrid

c# - 需要对后台工作人员的建议

Java - 绑定(bind)到特定子类的通用数字方法签名

iphone - 两个非常相似的应用程序,具有不同的文本和UI图形

c# - 如何检查列表列表是否包含另一个列表