定义
抽象工厂模式的实质就是提供接口来创建一系列相关或独立的对象而不指定这些对象的具体类。
理解
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时由于需求的变化,往往存在着更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象的创建方法(熟悉的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 = bonus.Calculate(); ChineseTax tax = new ChineseTax(); double taxValue = 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 = bonus.Calculate(); AmericanTax tax = new AmericanTax(); double taxValue = 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 = bonus.Calculate(); Tax tax = new ChineseTax(); double taxValue = 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> /// 客户端程序调用 ///
新闻热点
疑难解答