重构代码以避免类型转换

LCJ*_*LCJ 8 c# generics design-patterns domain-driven-design visitor

我在.Net 4.0中有以下C#代码.它需要对IRetailBusiness进行IBusiness类型转换.

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

if (bus is IIntellectualRights)
{
       investmentReturns.Add(new IntellectualRightsInvestmentReturn((IIntellectualRights)bus));
}
Run Code Online (Sandbox Code Playgroud)

业务场景:

我正在为投资控股公司设计软件系统.该公司拥有零售业务和IntellectualRights业务.BookShop和AudioCDShop是零售业务的例子.EngineDesignPatent和BenzolMedicinePatent是IntellectualRights业务的例子.这两种业务类型完全不相关.

投资公司有一个概念叫InvestmentReturn (但每个企业对这个概念完全无知).InvestmentReturn是从每个业务获得的利润,并使用它进行计算ProfitElement.对于每种"业务类型"(Retail,IntellectualRights),使用的ProfitElement是不同的.

如何重构这个一流的设计,以避免这种情况type castingtype checking

摘要投资

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

扩展

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

客户

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

业务域实体

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
Run Code Online (Sandbox Code Playgroud)

参考

  1. 重构我的代码:避免在派生类中进行强制转换
  2. 在C#中转换为泛型类型
  3. Visitor实现如何处理未知节点
  4. 在C#中打开封闭原则和访问者模式实现

Kit*_*Kit 5

此解决方案使用业务接口知道必须创建返回的概念,并且他们的具体实现知道要创建什么样的具体返回.

步骤1分割InvestmentReturn成两个接口; 原始减去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; }        
}
Run Code Online (Sandbox Code Playgroud)

步骤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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public interface IBusiness
{
    InvestmentReturn GetReturn();
}
Run Code Online (Sandbox Code Playgroud)

步骤4介绍一个通用的sublcass,EntityBaseClass以提供上述方法的默认实现.如果您不这样做,您将必须为所有业务实施它.如果你这样做,这意味着你不想重复GetReturn()实现的所有类必须从下面的类继承,这反过来意味着它们必须继承EntityBaseClass.

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

步骤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);
    // }
}
Run Code Online (Sandbox Code Playgroud)

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

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

如果你不想让你的具体企业知道什么有关创建InvestmentReturn-只知道,当他们必须创建一个要求,那么你可能要修改这个模式结合了工厂,创建返回给定的输入(例如之间的映射IBusiness实现和InvestmentReturn子类型).

变异

所有上述工作正常,如果您删除设置该Business属性的投资回报构造函数,将编译.这样做意味着Business在别处设置.这可能不太可取.

另一种方法是在Business里面设置属性GetReturn.我找到了一种方法来做到这一点,但它确实开始让课程看起来很混乱.它是在这里评估它是否值得.

从以下位置删除非默认构造函数RetailInvestmentReturn:

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

改变BusinessBaseClass.这是双重演员变得混乱的地方,但至少它只限于一个地方.

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 };
    }
}
Run Code Online (Sandbox Code Playgroud)

最后改变你的业务.这是一个例子BookShop:

public class BookShop : BusinessBaseClass<RetailInvestmentReturn, IRetailBusiness>, IRetailBusiness
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)