重构代码以避免类型转换

我在.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进行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 allMyProfitableBusiness = new List(); 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 investmentReturns = new List(); 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. 重构我的代码:避免在派生类中进行强制转换
  2. 在C#中转换为generics类型
  3. Visitor实现如何处理未知节点
  4. 在C#中打开封闭原则和访问者模式实现

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

步骤1拆分InvestmentReturn两个界面; 原始减去Business属性和新的通用子类:

 public abstract class InvestmentReturn { public double ProfitElement { get; set; } public abstract double GetInvestmentProfit(); public double CalculateBaseProfit() { // ... } } public abstract class InvestmentReturn: InvestmentReturn where T : IBusiness { public T Business { get; set; } } 

步骤2inheritance通用的,这样您就可以使用Business而不进行强制转换:

 public class RetailInvestmentReturn : InvestmentReturn { // 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(); } } 

步骤3IBusiness添加一个返回InvestmentReturn

 public interface IBusiness { InvestmentReturn GetReturn(); } 

步骤4引入EntityBaseClass的通用子级,以提供上述方法的默认实现。 如果您不这样做,您将必须为所有业务实施它。 如果你这样做,这意味着你不想重复GetReturn()实现的所有类必须从下面的类inheritance,这反过来意味着它们必须从EntityBaseClassinheritance。

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

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

 public class BookShop : BusinessBaseClass, 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(); // ... var investmentReturns = allMyProfitableBusiness.Select(bus => bus.GetReturn()).ToList(); // ... } 

如果您不希望您的具体企业了解有关创建InvestmentReturn 任何信息 – 只知道他们必须在被问到时创建一个 – 那么您可能希望修改此模式以合并一个创建返回给定输入的工厂(例如地图)在IBusiness实现和InvestmentReturn子类型之间)。

变异

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

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

RetailInvestmentReturn删除非默认构造RetailInvestmentReturn

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

更改BusinessBaseClass 。 这是一个混乱的地方,但至少它只限于一个地方。

 public abstract class BusinessBaseClass : EntityBaseClass, IBusiness where T : InvestmentReturn, 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, IRetailBusiness { // ... } 

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

 public interface IVisitor { T Visit(IIntellectualRights bus); T Visit(IRetailBusiness bus); } public interface IBusiness { T Accept(IVisitor 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 { 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); 

完全未经测试和未经validation..但这个概念有效。 如果您只有两种不同类型的节点应该访问,这也可能是一种过度杀伤。

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

您只需通过更改构造函数签名即可完成此操作:

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

至:

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

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

  1. 使用“类型”和“运行方法”创建字典
  2. 用它!

在代码中,看起来像这样:

  1. (创建,…)

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

// 试试这个:

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

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

如何使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 allMyProfitableBusiness = new List(); 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 investmentReturns = new List(); 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(); } } 

上面的所有答案看起来都比必要的复杂,但是你并没有真正说出你需要保留的信息,所以很难知道你可以在多长时间内重新设计你的课程。 如果你想做的只是产生上述程序产生的结果并仍然使用对象,你可以这样做:

 class Program2 { static void Main(string[] args) { List allMyProfitableBusiness = new List(); 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; } } 

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

 class Program { static void Main(string[] args) { List allMyProfitableBusiness = new List(); 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 

当然,任何人都会读到这个的机会非常非常小:-)