首页 > 代码库 > 设计模式(3)--抽象工厂模式(Absrtact Factory Pattern)
设计模式(3)--抽象工厂模式(Absrtact Factory Pattern)
定义
抽象工厂模式的实质就是提供接口来创建一系列相关或独立的对象而不指定这些对象的具体类。
理解
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时由于需求的变化,往往存在着更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象的创建方法(熟悉的new操作符),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?这就是我们要说的抽象工厂模式。抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。
结构
优点
- 分离了具体的类;
- 易于交换产品的系列;
- 有利于保持产品的一致性。
缺点
- 在产品族中扩展新的产品是很困难的,它需要修改抽象工厂的接口。
适用情形
- 一个系统不应该依赖于产品实例如何被创建、组合和表达的细节,这对于所有形态工厂模式的都是重要的;
- 提供一个产品类库,只想提供它的接口而不是它的实现类;
- 系统有多余一个的产品族但是系统只消费其中一个产品;
- 设计上要求属于同一产品族的产品是在一起使用的。
代码示例说明
(这里借用TerryLee大神的例子和代码)
中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。
员工的工资 = (基本工资 + 奖金 - 个人所得税)。这是一个放之四海皆准的运算法则。
为了简化系统,我们假设员工基本工资总是4000美金。
中国企业奖金和个人所得税的计算规则是:
奖金 = 基本工资(4000) * 10%
个人所得税 = (基本工资 + 奖金) * 40%
我们现在要为此构建一个软件系统(代号叫Softo),满足中国企业的需求。
此时采用最简单的方式,类图如下所示:
代码如下所示:
using System;namespace ChineseSalary{ /// <summary> /// 计算中国个人所得税 /// </summary> public class ChineseTax { public double Calculate() { return (Constant.BASE_SALARY + (Constant.BASE_SALARY * 0.1)) * 0.4; } }}using System;namespace ChineseSalary{ /// <summary> /// 计算中国个人奖金 /// </summary> public class ChineseBonus { public double Calculate() { return Constant.BASE_SALARY * 0.1; } }}namespace ChineseSalary{ /// <summary> /// 公用的常量 /// </summary> public class Constant { public static double BASE_SALARY = 4000; }}using System;namespace ChineseSalary{ /// <summary> /// 客户端程序调用 /// </summary> public class Calculator { public static void Main(string[] args) { ChineseBonus bonus = new ChineseBonus(); double bonusValue =http://www.mamicode.com/ bonus.Calculate(); ChineseTax tax = new ChineseTax(); double taxValue =http://www.mamicode.com/ tax.Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine("Chinaese Salary is:" + salary); Console.ReadLine(); } }}
美国企业工资的计算与中国大致相同,但是在奖金和个人所得税计算规则不同于中国:
奖金 = 基本工资 * 15 %
个人所得税 = (基本工资 * 5% + 奖金 * 25%)
我们仅仅将ChineseTax、ChineseBonus修改为AmericanTax、AmericanBonus。 只是修改了部分类的名称和类方法的内容,结构没有什么变化,修改后的类图如下所示:
代码如下所示:
using System;namespace AmericanSalary{ /// <summary> /// 计算美国个人奖金 /// </summary> public class AmericanBonus { public double Calculate() { return Constant.BASE_SALARY * 0.1; } }}using System;namespace AmericanSalary{ /// <summary> /// 计算美国个人所得税 /// </summary> public class AmericanTax { public double Calculate() { return (Constant.BASE_SALARY + (Constant.BASE_SALARY * 0.1)) * 0.4; } }}using System;namespace AmericanSalary{ /// <summary> /// 公用的常量 /// </summary> public class Constant { public static double BASE_SALARY = 4000; }}using System;namespace AmericanSalary{ /// <summary> /// 客户端程序调用 /// </summary> public class Calculator { public static void Main(string[] args) { AmericanBonus bonus = new AmericanBonus(); double bonusValue =http://www.mamicode.com/ bonus.Calculate(); AmericanTax tax = new AmericanTax(); double taxValue =http://www.mamicode.com/ tax.Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine("American Salary is:" + salary); Console.ReadLine(); } }}
开始只考虑将Softo系统运行于中国企业,但随着MaxDO公司业务向海外拓展, MaxDO需要将该系统移植给美国使用。需要将上面中国和美国两种情况整合在一个系统。移植时,MaxDO不得不抛弃中国企业的业务规则类ChineseTax和ChineseBonus, 然后为美国企业新建两个业务规则类: AmericanTax,AmericanBonus。最后修改了业务规则调用Calculator类。
结果我们发现:每当Softo系统移植的时候,就抛弃原来的类。现在,如果中国华为要购买该系统,我们不得不再次抛弃AmericanTax,AmericanBonus,修改回原来的业务规则。一个可以立即想到的做法就是在系统中保留所有业务规则模型,即保留中国和美国企业工资运算规则。
前面系统的整合问题在于:当系统在客户在美国和中国企业间切换时仍然需要修改Caculator代码。
一个维护性良好的系统应该遵循“开闭原则”。即:封闭对原来代码的修改,开放对原来代码的扩展(如类的继承,接口的实现)
我们发现不论是中国企业还是美国企业,他们的业务运规则都采用同样的计算接口。 于是很自然地想到建立两个业务接口类Tax,Bonus,然后让AmericanTax、AmericanBonus和ChineseTax、ChineseBonus分别实现这两个接口,类图如下所示:
具体代码如下所示:
using System;namespace InterfaceSalary{ /// <summary> /// 奖金抽象类 /// </summary> public abstract class Bonus { public abstract double Calculate(); }}using System;namespace InterfaceSalary{ /// <summary> /// 计算中国个人奖金 /// </summary> public class ChineseBonus:Bonus { public override double Calculate() { return Constant.BASE_SALARY * 0.1; } }}using System;namespace InterfaceSalary{ /// <summary> /// 个人所得税抽象类 /// </summary> public abstract class Tax { public abstract double Calculate(); }}using System;namespace InterfaceSalary{ /// <summary> /// 计算中国个人所得税 /// </summary> public class ChineseTax:Tax { public override double Calculate() { return (Constant.BASE_SALARY + (Constant.BASE_SALARY * 0.1)) * 0.4; } }}using System;namespace InterfaceSalary{ /// <summary> /// 公用的常量 /// </summary> public class Constant { public static double BASE_SALARY = 4000; }}using System;namespace InterfaceSalary{ /// <summary> /// 客户端程序调用 /// </summary> public class Calculator { public static void Main(string[] args) { Bonus bonus = new ChineseBonus(); double bonusValue =http://www.mamicode.com/ bonus.Calculate(); Tax tax = new ChineseTax(); double taxValue =http://www.mamicode.com/ tax.Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine("Chinaese Salary is:" + salary); Console.ReadLine(); } }}
然而,上面增加的接口几乎没有解决任何问题,因为当系统的客户在美国和中国企业间切换时Caculator代码仍然需要修改。只不过修改少了两处,但是仍然需要修改ChineseBonus,ChineseTax部分。致命的问题是:我们需要将这个移植工作转包给一个叫Hippo的软件公司。 由于版权问题,我们并未提供Softo系统的源码给Hippo公司,因此Hippo公司根本无法修改Calculator,导致实际上移植工作无法进行。
为此,我们考虑增加一个工具类(命名为Factory),代码如下:
using System;namespace FactorySalary{ /// <summary> /// Factory类 /// </summary> public class Factory { public Tax CreateTax() { return new ChineseTax(); } public Bonus CreateBonus() { return new ChineseBonus(); } }}
using System;namespace FactorySalary{ /// <summary> /// 客户端程序调用 /// </summary> public class Calculator { public static void Main(string[] args) { Bonus bonus = new Factory().CreateBonus(); double bonusValue =http://www.mamicode.com/ bonus.Calculate(); Tax tax = new Factory().CreateTax(); double taxValue =http://www.mamicode.com/ tax.Calculate(); double salary = 4000 + bonusValue - taxValue; Console.WriteLine("Chinaese Salary is:" + salary); Console.ReadLine(); } }}
不错,我们解决了一个大问题,设想一下:当该系统从中国企业移植到美国企业时,我们现在需要做什么?
答案是: 对于Caculator类我们什么也不用做。我们需要做的是修改Factory类。
很显然,前面的解决方案带来了一个副作用:就是系统不但增加了新的类Factory,而且当系统移植时,移植工作仅仅是转移到Factory类上,工作量并没有任何缩减,而且还是要修改系统的源码。 从Factory类在系统移植时修改的内容我们可以看出: 实际上它是专属于美国企业或者中国企业的。名称上应该叫AmericanFactory,ChineseFactory更合适.
解决方案是增加一个抽象工厂类AbstractFactory,增加一个静态方法,该方法根据一个配置文件(App.config或者Web.config) 一个项(比如factoryName)动态地判断应该实例化哪个工厂类,这样,我们就把移植工作转移到了对配置文件的修改。修改后的类图如下:
抽象工厂类的代码如下(使用反射模式):
using System;using System.Reflection;namespace AbstractFactory{ /// <summary> /// Factory类 /// </summary> public abstract class AbstractFactory {// public AbstractFactory GetInstance()// {// string factoryName = Constant.STR_FACTORYNAME.ToString();//// AbstractFactory instance;//// if(factoryName == "ChineseFactory")// instance = new ChineseFactory();// else if(factoryName == "AmericanFactory")// instance = new AmericanFactory();// else// instance = null;//// return instance;// } public AbstractFactory GetInstance() { string factoryName = Constant.STR_FACTORYNAME.ToString(); AbstractFactory instance; if(factoryName != "") instance = (AbstractFactory)Assembly.Load(factoryName).CreateInstance(factoryName); else instance = null; return instance; } public abstract Tax CreateTax(); public abstract Bonus CreateBonus(); }}
配置文件如下所示: