重构代码以避免类型转换

时间:2014-01-31 14:47:03

标签: 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));
}

业务场景:

我正在为投资控股公司设计软件系统。该公司拥有零售业务和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;
    }
}

扩展

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#

6 个答案:

答案 0 :(得分: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; }        
}

第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()实现的所有类必须从下面的类继承,这反过来意味着它们必须继承自{{ 1}}。

EntityBaseClass

步骤5 为每个子类实施该方法(如有必要)。以下是public abstract class BusinessBaseClass<T> : EntityBaseClass, IBusiness where T : InvestmentReturn, new() { public virtual InvestmentReturn GetReturn() { return new T(); } }

的示例
BookShop

第6步修改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); // } } 以添加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内设置Business属性。我找到了一种方法来做到这一点,但它确实开始让课程看起来很混乱。这是为了评估它是否值得。

GetReturn中删除非默认构造函数:

RetailInvestmentReturn

更改public class RetailInvestmentReturn : InvestmentReturn<IRetailBusiness> { public override double GetInvestmentProfit() { ProfitElement = Business.GrossRevenue; return CalculateBaseProfit(); } } 。这是双重演员变得混乱的地方,但至少它只限于一个地方。

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 }; } } 的示例:

BookShop

答案 1 :(得分:3)

通过访客模式访问IBusiness的具体实现。然后在投资回报域中创建一个可以访问每个业务的访问者,如下所示:

public interface IVisitor<T>
{
    T Visit(IIntellectualRights bus);
    T Visit(IRetailBusiness bus);
}

public interface IBusiness
{
    T Accept<T>(IVisitor<T> visitor);
}

public class AudioCDShop : EntityBaseClass, IRetailBusiness
{
     public void Accept(IVisitor visitor)
     {
          return visitor.Visit(this);
     }

//do the same for each IBusiness implementor.

然后在您的投资回报域中:

 public class InvestmentVisitor : IVisitor<InvestmentReturn>
 {
     public InvestmentReturn GetInvestment(IBusiness bus)
     {
          return bus.Accept(this);
     }

     public InvestmentReturn Visit(IIntellectualRights bus)
     {
          return new IntellectualRightsInvestmentReturn(bus)
     }

     public InvestmentReturn  Visit(IRetailBusiness bus)
     {
          return new RetailInvestmentReturn(bus);
     }
 }

用法

 var investmentReturn = new InvestmentVisitor().GetInvestment(bus);

完全未经测试且未经验证..但这个概念有效。 如果您只有两种不同类型的节点应该访问,那么这也可能有点过分。

答案 2 :(得分:1)

基本上,如果我理解你,你想避免类型转换。

您只需通过以下方式更改构造函数签名即可完成此任务:

public RetailInvestmentReturn(IRetailBusiness retail) {...}
public IntellectualRightsInvestmentReturn(IIntellectualRights intellectual) {...}

为:

public RetailInvestmentReturn(IBusiness retail) {...}
public IntellectualRightsInvestmentReturn(IBusiness intellectual) {...}

如果由于设计限制而不是选项,那么您可以尝试使用策略模式。那仍然需要一个类型演员,但你会摆脱可怕的“ifs”和“elses”,我相信,这是你真正的问题,对吧?为此,您需要:

  1. 使用“type”和“要运行的方法”
  2. 创建一个字典
  3. 使用它!
  4. 在代码中,看起来像这样:

    1. (创建,...)

          Dictionary<Type,Func<IBusiness,InvestmentReturn>> dict = new Dictionary<Type, Func<IBusiness, InvestmentReturn>>
          {
              {typeof (BookShop), business => new RetailInvestmentReturn((IRetailBusiness) business)},
              {typeof (AudioCDShop), business => new IntellectualRightsInvestmentReturn((IIntellectualRights) business)}
          };
      
    2. (使用它!)

    3. //试试这个:

      foreach (IBusiness bus in allMyProfitableBusiness)
      {
          investmentReturns.Add(dict[bus.GetType()](bus));
      }
      

答案 3 :(得分:0)

让IBusiness接口处理InvestmentReturn类型并返回利润金额。

对foreach循环的更改:

foreach (IBusiness bus in allMyProfitableBusiness)
{
    // No type checking or casting.  It is scalable to new business types.
    investmentReturns.Add(bus.GetReturnInvestment());
}

接口更新:

public interface IBusiness
{
    IReturnInvestment GetReturnInvestment();

    double GetProfit();
}

public abstract class EntityBaseClass
{

}

public interface IRetailBusiness : IBusiness
{
    ...
}

public interface IIntellectualRights : IBusiness
{
    ...
}

包含其他基类的业务类:

#region Intellectuals
public abstract IntellectualRightsBaseClass : EntityBaseClass, IIntellectualRights
{
    ...
    public IReturnInvestment GetReturnInvestment()
    {
        return new IntellectualRightsInvestmentReturn(this);
    }

    public double GetProfit()
    {
        return this.Royalty;
    }
}

public class EngineDesignPatent : IntellectualRightsBaseClass
{
    ...

}

public class BenzolMedicinePatent : IntellectualRightsBaseClass
{
    ...
}
#endregion

#region Retails
public abstract RetailBusinessBaseClass : IRetailBusiness
{
    ...
    public IReturnInvestment GetReturnInvestment()
    {
        return new RetailInvestmentReturn(this);
    }

    public double GetProfit()
    {
        return this.GrossRevenue;
    }
}

public class BookShop : RetailBusinessBaseClass 
{
    ...
}

public class AudioCDShop : RetailBusinessBaseClass 
{
    ...
}
#endregion

扩展

public class RetailInvestmentReturn : InvestmentReturn
{
    public RetailInvestmentReturn(IRetailBusiness retail)
        : base(retail)
    {
    }

    /* Don't need anymore
    public override  double GetInvestmentProfit()
    {
        //GrossRevenue is the ProfitElement for RetailBusiness
        ProfitElement = Business.GetProfit();
        return base.CalculateBaseProfit();
    } */
}

public class IntellectualRightsInvestmentReturn : InvestmentReturn
{

    public IntellectualRightsInvestmentReturn(IIntellectualRights intellectual)
        : base(intellectual)
    {
    }

    /* Don't need anymore
    public override double GetInvestmentProfit()
    {
        //Royalty is the ProfitElement for IntellectualRights Business
        ProfitElement = Business.GetProfit();
        return base.CalculateBaseProfit();
    } */
}

抽象投资返回

public abstract class InvestmentReturn
{
    protected InvestmentReturn(IBusiness business)
    {
        this.Business = business;
    }

    public double ProfitElement { get; set; }
    public IBusiness Business{ get;  set; }

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

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

答案 4 :(得分:0)

如何使InvestmentReturn成为具体的类并将ProfitElement逻辑转移到IBusiness?

public class InvestmentReturn
{
    public double ProfitElement { get; set; }

    public double InvestmentProfit{ get; set; }

    public RetailInvestmentReturn(IRetailBusiness bus)
    {
        ProfitElement = bus.GetProfitElement();
    }

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

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

    public override double GetProfitElement() 
    {
        return royalty;
    }    
}



public class BookShop : EntityBaseClass, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public BookShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }
    public override double GetProfitElement() 
    {
        return royalty;
    } 
}

现在客户端变为:

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)
        {
            investmentReturns.Add(new InvestmentReturn(bus));
        }

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

        Console.ReadKey();
    }
}

答案 5 :(得分:0)

上面的所有答案似乎都比必要的更复杂,但你并不能确切地说出你需要保留哪些信息,所以很难知道你可以重新设计你的信息。类。如果你想做的只是产生上述程序产生的结果并仍然使用对象,你可以这样做:

class Program2
{
    static void Main(string[] args)
    {
        List<Business> allMyProfitableBusiness = new List<Business>();

        Business bookShop1 = new Business(75);
        Business cd1Shop = new Business(80);
        Business enginePatent = new Business(1200);
        Business medicinePatent = new Business(1450);

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

        foreach (var business in allMyProfitableBusiness)
        {
            Console.WriteLine("Profit: {0:c}", business.GetInvestmentProfit());
        }

        Console.ReadKey();
    }
}


public class Business
{
    private double ProfitElement;

    public Business(double profitElement)
    {
        ProfitElement = profitElement;
    }

    public double GetInvestmentProfit()
    {
        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;
    }
}

如果由于某种原因,您确实需要保留不同类型的业务(零售和知识产权),您仍然可以使用基类大大简化它。您不需要泛型或InvestmentReturnEntityBaseClass(虽然您计算totalProfit,但不会在任何地方使用):

class Program
{
    static void Main(string[] args)
    {
        List<Business> allMyProfitableBusiness = new List<Business>();

        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);

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

        Console.ReadKey();
    }
}


public abstract class Business
{
    public abstract double Profit { get; }

    public double GetInvestmentProfit()
    {
        double profit = 0;

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

        return profit;
    }
}


public abstract class RetailBusiness : Business
{
    public double GrossRevenue { get; set; }
    public override double Profit {get {return GrossRevenue; } }
}

public abstract class IntellectualRights : Business
{
    public double Royalty { get; set; }
    public override double Profit {get {return Royalty; } }
}


#region Intellectuals
public class EngineDesignPatent : IntellectualRights
{
    public EngineDesignPatent(double royalty)
    {
        Royalty = royalty;
    }
}

public class BenzolMedicinePatent : IntellectualRights
{
    public BenzolMedicinePatent(double royalty)
    {
        Royalty = royalty;
    }
}
#endregion


#region Retails
public class BookShop : RetailBusiness
{
    public BookShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }
}

public class AudioCDShop : RetailBusiness
{
    public AudioCDShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }
}
#endregion

当然,任何人读这篇文章的机会非常非常小: - )