设计模式(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
{
;
}
}
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();
+ 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
{
;
}
}
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();
+ 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
{
;
}
}
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();
+ 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 = bonus.Calculate();
Tax tax = new Factory().CreateTax();
double taxValue = tax.Calculate();
+ 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();
}
}
配置文件如下所示:

设计模式(3)--抽象工厂模式(Absrtact Factory Pattern)的更多相关文章
- 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern)
原文:乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factor ...
- 【设计模式】抽象工厂模式 Abstract Factory Pattern
简单工厂模式是一个工厂类根据工厂方法的参数创建不出不同的产品, 工厂方法模式是每一个产品都有一个一一对应的工厂负责创建该产品.那么今天要讲的抽象工厂模式是一个工厂能够产生关联的一系列产品.抽象工厂模式 ...
- 二十四种设计模式:抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式(Abstract Factory Pattern) 介绍提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 示例有Message和MessageModel,Messag ...
- 【UE4 设计模式】抽象工厂模式 Abstract Factory Pattern
概述 描述 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类:具体的工厂负责实现具体的产品实例 抽象工厂中每个工厂可以创建多种产品(如苹果公司生产iPhone.iPad): 工厂方法 ...
- 设计模式之抽象工厂模式(Abstract Factory Pattern)
一.抽象工厂模式的由来 抽象工厂模式,最开始是为了解决操作系统按钮和窗体风格,而产生的一种设计模式.例如:在windows系统中,我们要用windows设定的按钮和窗体,当我们切换Linux系统时,要 ...
- 设计模式 - 抽象工厂模式(abstract factory pattern) 具体解释
抽象工厂模式(abstract factory pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/2709 ...
- 【设计模式】简单工厂模式 Simple Factory Pattern
简单工厂模式Simple Factory Pattern[Simple Factory Pattern]是设计模式里最简单的一个模式,又叫静态工厂模式[Static Factory Pattern], ...
- Net设计模式实例之抽象工厂模式(Abstract Factory Pattern)
一.抽象工厂模式简介(Bref Introduction) 抽象工厂模式(Abstract Factory Pattern),提供一个创建一系列相关或者相互依赖对象的接口,而无需制定他们的具体类.优点 ...
- C#设计模式——抽象工厂模式(Abstract Factory Pattern)
一.概述在软件开发中,常常会需要创建一系列相互依赖的对象,同时,由于需求的变化,往往存在较多系列对象的创建工作.如果采用常规的创建方法(new),会造成客户程序和对象创建工作的紧耦合.对此,抽象工厂模 ...
- 六个创建模式之抽象工厂模式(Abstract Factory Pattern)
问题: 使用工厂方法模式的主要问题是工厂类过多,每个产品对应一个工厂,不利于维护.因此可以考虑使用一个工厂创建一个产品族. 定义: 提供创建一些列相关或相互依赖的对象实例的接口,这些类可以称为一个产品 ...
随机推荐
- MS SQL-Server快捷键
快捷键 功能 Ctrl+Shift+B 生成解决方案 Ctrl+F7 生成编译 Ctrl+O ...
- Tutorial - Deferred Rendering Shadow Mapping 转
http://www.codinglabs.net/tutorial_opengl_deferred_rendering_shadow_mapping.aspx Tutorial - Deferred ...
- linux下的nodejs安装
linux下安装nodejs的方式: 1.源码安装 2.nvm安装 这里推荐使用nvm安装,避免下载nodejs源码: 安装步骤: 一.安装git 一般linux系统的git版本 ...
- 事务管理(下) 配置spring事务管理的几种方式(声明式事务)
配置spring事务管理的几种方式(声明式事务) 概要: Spring对编程式事务的支持与EJB有很大的区别.不像EJB和Java事务API(Java Transaction API, JTA)耦合在 ...
- 后缀树(suffix tree)
参考: 从前缀树谈到后缀树 后缀树 Suffix Tree-后缀树 字典树(trie树).后缀树 一.前缀树 简述:又名单词查找树,tries树,一种多路树形结构,常用来操作字符串(但不限于字符串), ...
- mybatis2
正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Sessio ...
- android APK更新
菜鸟的博客请多多指教 最近做了一个新功能,更新APK的功能 1.更新APK是一个耗时的任务,我采用了一个服务来做,上次在网上看到服务是在主线程里面,自己也测试了下,数据是真的 所以下载动作还必须在服务 ...
- 关于启用 HTTPS 的一些经验分享(二)
转载: 关于启用 HTTPS 的一些经验分享(二) 几天前,一位朋友问我:都说推荐用 Qualys SSL Labs 这个工具测试 SSL 安全性,为什么有些安全实力很强的大厂家评分也很低?我认为这个 ...
- Android开发之无线遥控器
最近弄了一个UDP/TCP的小东西,主要需要实现的功能如下(服务器端): 1.基于局域网 2.服务器端网络接口为无线与有线 3.服务器端接收到客户端的数据需要模拟按键进行处理 4.开机自启动 5.使用 ...
- java和android的环境变量配置
Java环境变量配置: 1.新建系统变量 变量名:JAVA_HOME 变量值:F:\JAVA\JDK(自己的JDK文件路径) 2.在系统变量path后面添加:%JAVA_HOME%\bin; And ...