设计模式系列一创建型之(抽象工厂模式)
1、抽象工厂简介
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时由于需求的变化,往往存在着更多系列对象的创建工作。
- 如何应对这种变化?
- 如何绕过常规的对象的创建方法(new)?
- 如何来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
然而抽象工厂便可以很好地解决这个问题!
2、意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
3、适用性
- 一个系统要独立与它的产品创建、组合和表示时
- 一个系统要由多个产品系列中的一个来配置时
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时
- 当你提供一个产品类库,而只想显示它们的接口而不是实现时
4、结构图
5、应用实例
注:此案例,借鉴网络资料;比较易于学习,所以自己练习了下,在这里也给大家分享了!
中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。
员工工资 = 基本工资 + 奖金 - 个人所得税 ; 为了简化系统,这里定义员工基本工资:4000
中国企业奖金和个人所得税的计算规则是:
奖金 = 基本工资 * 10%
个人所得税 =(基本工资 + 奖金) * 40%
为此我们要构造一个系统,满足中国企业的需要:(名称Softo)
/// <summary> /// 公用变量基本工资 /// </summary> public class Constant { public static double base_salary = 4000; } /// <summary> /// 计算中国个人奖金 /// </summary> public class ChinaBonus { public double Calculate() { return Constant.base_salary * 0.1; } } /// <summary> /// 计算个人所得税 /// </summary> public class ChinaTax { public double Calculate() { return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4; } }
客户端调用:
static void Main(string[] args) { double bonusValue_China = new ChinaBonus().Calculate(); double taxValue_China = new ChinaTax().Calculate(); double salary_China = 4000 + bonusValue_China - taxValue_China; Console.WriteLine("China Salary is:" + salary_China); }
运行结果:
以上满足了中国企业需求,但为了拓展国际市场,需求发生变更;我们要把该系统移植给美国公司使用,员工工资计算规则不变;但是奖金和个人所得税不同于中国,其规则如下:
奖金 = 基本工资 * 15%
个人所得税 = 基本工资 * 5% + 奖金 * 25%
根据现有系统,我们只需要做如下更改:
/// <summary> /// 公用变量基本工资 /// </summary> public class Constant { public static double base_salary = 4000; } /// <summary> /// 计算美国个人奖金 /// </summary> public class AmericanBonus { public double Calculate() { return Constant.base_salary * 0.1; } } /// <summary> /// 计算美国个人所得税 /// </summary> public class AmericanTax { public double Calculate() { return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4; } }
客户端调用:
static void Main(string[] args) { double bonusValue_American = new AmericanBonus().Calculate(); double taxValue_American = new AmericanTax().Calculate(); double salary_American = 4000 + bonusValue_American - taxValue_American; Console.WriteLine("American Salary is:" + salary_American); Console.ReadLine(); }
运行结果:
为了以后业务拓展,我们打算把Softo整合为通用系统:
比较以上两个系统,业务规则类发生了变化,客户端调用发生了变化,如果要做通用的就必须保留所有的业务规则模型,在中国与美国之间切换时,只需要修改客户端调用即可。
但是,一个维护性良好的系统应该遵循“开闭原则”。即:封闭对原来代码的修改,开放对原来代码的扩展 (如类的继承,接口的实现)。我们发现不论是中国企业还是美国企业,他们的业务运规则都采用同样的计算接口,于是修改如下:
class Program { static void Main(string[] args) { IBonus bonus = new ChinaBonus(); double bonusValue_China = bonus.Calculate(); ITax tax = new ChinaTax(); double taxValue_China = tax.Calculate(); double salary_China = 4000 + bonusValue_China - taxValue_China; Console.WriteLine("China Salary is:" + salary_China); Console.ReadLine(); } } /// <summary> /// 公用变量基本工资 /// </summary> public class Constant { public static double base_salary = 4000; } public interface IBonus { double Calculate(); } public interface ITax { double Calculate(); } /// <summary> /// 计算中国个人奖金 /// </summary> public class ChinaBonus : IBonus { public double Calculate() { return Constant.base_salary * 0.1; } } /// <summary> /// 计算个人所得税 /// </summary> public class ChinaTax : ITax { public double Calculate() { return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4; } } /// <summary> /// 计算美国个人奖金 /// </summary> public class AmericanBonus : IBonus { public double Calculate() { return Constant.base_salary * 0.1; } } /// <summary> /// 计算美国个人所得税 /// </summary> public class AmericanTax : ITax { public double Calculate() { return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4; } }
运行结果:
然而,上面增加的接口几乎没有解决任何问题,因为当系统的客户在美国和中国企业间切换时,客户端仍然需要修改;
为此增加一个工具类Factory:
public class Factory { public IBonus CreateBonus() { return new ChinaBonus(); } public ITax CreateTax() { return new ChinaTax(); } }
客户端调用:
static void Main(string[] args) { IBonus bonus = new Factory().CreateBonus(); double bonusValue = bonus.Calculate(); ITax tax = new Factory().CreateTax(); double taxValue = tax.Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine(" Salary is:" + salary); Console.ReadLine(); }
运行结果:
此时,如果我们把该系统移植到美国,只需修改Factory工具类,把ChinaBonus替换为AmericanBonus;ChinaTax替换为AmericanTax即可;其实这也有一个副作用,新建了一个Factory类,并且把修改点转移到该类中,并没有解决根本问题;而且这个工具类可能是专属于美国企业或者中国企业的,名称叫:AmericanFactory,ChineseFactory更合适。那我们该解决这个问题?
此时才引入重点,添加抽象工厂方法:
增加一个静态方法,该方法根据一个配置文件动态地判断应该实例化哪个工厂类;
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="factoryName" value="ChinaFactory"></add> </appSettings> </configuration>
/// <summary> /// AbstractFactory /// </summary> public abstract class AbstractFactory { public static AbstractFactory GetInstance() { string factoryName = ConfigurationManager.AppSettings["factoryName"]; AbstractFactory instance; switch (factoryName) { case "ChinaFactory": instance = new ChinaFactory(); break; case "AmericanFactory": instance = new AmericanFactory(); break; default: instance = null; break; } return instance; } public abstract IBonus CreateBonus(); public abstract ITax CreateTax(); } public class ChinaFactory : AbstractFactory { public override IBonus CreateBonus() { return new ChinaBonus(); } public override ITax CreateTax() { return new ChinaTax(); } } public class AmericanFactory : AbstractFactory { public override IBonus CreateBonus() { return new AmericanBonus(); } public override ITax CreateTax() { return new AmericanTax(); } }
客户端调用:
static void Main(string[] args) { AbstractFactory instanceFac = AbstractFactory.GetInstance(); double bonusValue = instanceFac.CreateBonus().Calculate(); double taxValue = instanceFac.CreateTax().Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine(" Salary is:" + salary); Console.ReadLine(); }
运行结果:
此时当系统在美国企业和中国企业之间切换时,我们只需要修改配置改为AmericanFactory即可。
相信你看到这里也会赶脚到抽象工厂的强大吧!其实以上解决方案并不是最完美的,不知是否满过你的锐眼?
抽象工厂类中采用了分支判断,一旦业务更加广泛,不只是美国,有拓展至德国,此时我们不但要增加新的业务规则类:德国Tax、德国Bonus分别实现ITax和IBonus接口,新增德国Factory继承自AbstractFactory,而且还要添加AbstractFactory中的case分支,这依然不能满足OCP!至于该如何解决该问题,且看下节分析!
6、总结
- 最后使用抽象工厂模式后,我们会发现客户端完全依赖于抽象类,它不必去理解中国和美国企业具体的业务规则如何实现;面对的只是业务规则接口IBonus和ITax;从而把业务规则与客户端调用完全分离,从而降低耦合度;
- 完完全全地理解抽象工厂模式的意义非常重大,可以说对它的理解是你对OOP理解上升到一个新的里程碑的重要标志。学会了用抽象工厂模式编写框架类,你将理解OOP的精华:面向接口编程。
注:虽然案例是搜集的资料,但通过自己的整理与测试!希望博友能够尊重我的劳动成果,大家互相学习、共同进步!