重构代码以避免类型转换
我在.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 casting
和type 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
参考
- 重构我的代码:避免在派生类中进行强制转换
- 在C#中转换为generics类型
- Visitor实现如何处理未知节点
- 在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(); } }
步骤3向IBusiness
添加一个返回InvestmentReturn
:
public interface IBusiness { InvestmentReturn GetReturn(); }
步骤4引入EntityBaseClass
的通用子级,以提供上述方法的默认实现。 如果您不这样做,您将必须为所有业务实施它。 如果你这样做,这意味着你不想重复GetReturn()
实现的所有类必须从下面的类inheritance,这反过来意味着它们必须从EntityBaseClass
inheritance。
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”,我相信,这是你真正的问题,对吧? 为此,您需要:
- 使用“类型”和“运行方法”创建字典
- 用它!
在代码中,看起来像这样:
-
(创建,…)
Dictionary
> dict = new Dictionary > { {typeof (BookShop), business => new RetailInvestmentReturn((IRetailBusiness) business)}, {typeof (AudioCDShop), business => new IntellectualRightsInvestmentReturn((IIntellectualRights) business)} }; -
(用它!)
// 试试这个:
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或您的InvestmentReturn
或EntityBaseClass
(虽然您计算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
当然,任何人都会读到这个的机会非常非常小:-)