上一篇的工厂方法模式引入了工厂等级结构,解决了在原来简单工厂模式中工厂类职责太重的原则,但是由于工厂方法模式的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,从而增加系统开销。那么,我们应该怎么来重构?似乎,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是本次将要学习的抽象工厂模式的基本思想。

抽象工厂模式(Abstract Factory) 学习难度:★★★★☆ 使用频率:★★★★★

一、界面皮肤库的初始设计

  M公司IT开发部接到一个开发任务,想要对以前的一个系统开发一套界面皮肤库,可以对该桌面系统软件进行界面美化。这样,用户就可以在使用时通过菜单来选择皮肤,不同的皮肤将提供视觉效果不同的按钮、文本框以及组合框等界面元素,其结构示意图如下所示:

  该皮肤库需要具备良好的灵活性和可扩展性,用户可以自由选择不同的皮肤,开发人员也可以在不修改既有代码的基础上增加新的皮肤。

  M公司的开发人员针对上述需求,决定现学现卖,在上次使用了工厂方法模式之后对工厂方法模式大为赞赏,决定使用工厂方法模式来进行系统的设计,为了保证系统的灵活性和可扩展性,提供一系列具体工厂来创建按钮、文本框以及组合框等界面元素,客户端针对抽象工厂来编程,初始结构如下图所示:

  从上图可以看出,此方案提供了大量的工厂来创建具体的界面组件,可以通过配置文件来更换具体界面组件从而改变界面的风格,但是,此方案存在以下问题:

  (1)需要增加新的皮肤时,虽然不需要更改现有代码,但是需要增加大量的类,针对每一个新增具体组件都要增加一个具体工厂,类的个数会成对增加。这无疑会导致系统越来越庞大,从而增加了系统的维护成本和运行开销

  (2)由于同一种风格的不同界面组件通常需要一起显示,因此需要为每个组件都选择一个具体工厂,用户在使用时必须逐个进行设置,如果某个具体工厂选择失误将会导致界面显示混乱,虽然可以适当增加一些约束语句,但是客户端代码和配置文件都较为复杂。

  综上所述,如何减少系统中类的个数并保证客户端每次始终就只使用一种风格的具体界面组件?这是萦绕在M公司开发人员心头的两个问题。

二、抽象工厂模式介绍

2.1 理清产品等级与产品族

  (1)产品等级

  产品等级即产品的继承结构,例如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌电视机则是子类。

  (2)产品族

  产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组,例如海尔电器厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,而海尔电冰箱则位于电冰箱产品等级结构中,他们俩构成了一个产品族。

  产品等级与产品族的示意图如下图所示:

  可以看出,当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时,就可以使用抽象工厂模式。抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式。

Note :抽象工厂与工厂方法最大的区别就在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级中的产品对象的创建。

2.2 抽象工厂模式概述

  抽象工厂模式为创建一组对象提供了一种方案,与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。其定义如下:

Definition :抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。

  抽象工厂模式的结构图如下图所示:

  (1)Abstract Factory (抽象工厂角色):声明了一组用于创建一族产品的方法,每一个方法对应一种产品。

  (2)Concrete Factory (具体工厂角色):实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族。

  (3)Abstract Product (抽象产品角色):为每种产品声明接口,在抽象产品中声明了所有的业务方法。

  (4)Concrete Product (具体产品角色):定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法。

三、界面皮肤库的重构设计

3.1 抽象工厂模式设计方案

  M公司使用抽象工厂模式来重构了界面皮肤库的设计,其基本结构如下图所示:

3.2 重构代码

  (1)Abstract Product

    public interface IButton
{
void Display();
} public interface ITextField
{
void Display();
} public interface IComboBox
{
void Display();
}

  (2)Concrete Product

  ① Spring风格Button

    public class SpringButton : IButton
{
public void Display()
{
Console.WriteLine("显示浅绿色按钮...");
}
} public class SpringTextField : ITextField
{
public void Display()
{
Console.WriteLine("显示绿色边框文本框...");
}
} public class SpringComboBox : IComboBox
{
public void Display()
{
Console.WriteLine("显示绿色边框下拉框...");
}
}

  ② Summer风格Button

    public class SummerButton : IButton
{
public void Display()
{
Console.WriteLine("显示浅蓝色按钮...");
}
} public class SummerTextField : ITextField
{
public void Display()
{
Console.WriteLine("显示蓝色边框文本框...");
}
} public class SummerComboBox : IComboBox
{
public void Display()
{
Console.WriteLine("显示蓝色边框下拉框...");
}
}

  (3)Abstract Factory

    public interface ISkinFactory
{
IButton CreateButton();
ITextField CreateTextField();
IComboBox CreateComboBox();
}

  (4)Concrete Factory

  ① Spring皮肤工厂

    // Spring皮肤工厂
public class SpringSkinFactory : ISkinFactory
{
public IButton CreateButton()
{
return new SpringButton();
} public IComboBox CreateComboBox()
{
return new SpringComboBox();
} public ITextField CreateTextField()
{
return new SpringTextField();
}
}

  ② Summer皮肤工厂

    public class SummerSkinFactory : ISkinFactory
{
public IButton CreateButton()
{
return new SummerButton();
} public IComboBox CreateComboBox()
{
return new SummerComboBox();
} public ITextField CreateTextField()
{
return new SummerTextField();
}
}

3.3 测试结果

  (1)客户端代码

    public class Program
{
public static void Main(string[] args)
{
ISkinFactory skinFactory = (ISkinFactory) AppConfigHelper.GetSkinFactoryInstance();
if (skinFactory == null)
{
Console.WriteLine("读取当前选中皮肤类型失败...");
} IButton button = skinFactory.CreateButton();
ITextField textField = skinFactory.CreateTextField();
IComboBox comboBox = skinFactory.CreateComboBox(); button.Display();
textField.Display();
comboBox.Display(); Console.ReadKey();
}
}

  其中,AppConfigHelper用于从以下的配置文件中读取SkinFactory的值去反射生成具体工厂实例,这里配置的当前选中皮肤是Spring风格。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- 当前选中皮肤类型 -->
<add key="SkinFactory" value="Manulife.ChengDu.DesignPattern.AbstractFactory.SpringSkinFactory, Manulife.ChengDu.DesignPattern.AbstractFactory"/>
</appSettings>
</configuration>

  AppConfigHelper的具体代码如下:

    public class AppConfigHelper
{
public static string GetSkinFactoryName()
{
string factoryName = null;
try
{
factoryName = System.Configuration.ConfigurationManager.AppSettings["SkinFactory"];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return factoryName;
} public static object GetSkinFactoryInstance()
{
string assemblyName = AppConfigHelper.GetSkinFactoryName();
Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type);
return instance;
}
}

  (2)调试结果

  终于到了兴奋的时刻啦,运行结果如下:

  

  这时,我们将配置文件中的值改为SummerSkinFactory试试:

<add key="SkinFactory" value="Manulife.ChengDu.DesignPattern.AbstractFactory.SummerSkinFactory, Manulife.ChengDu.DesignPattern.AbstractFactory"/>

  再次运行程序,结果是我们想要的:

  

四、抽象工厂模式总结

4.1 抽象工厂模式主要优点

  (1)隔离了具体类的生成,使得客户并不需要知道什么被创建。因为这种隔离,因此更换一个具体工厂就变得相对容易。

  (2)当一个产品族中的多个对象被设计称一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。

  (3)增加新的产品族很方便,无需修改已有系统,符合开闭原则。

4.2 抽象工厂模式主要缺点

  增加新的产品等级结构很麻烦增加新的产品等级结构很麻烦,增加新的产品等级结构很麻烦!!!(重要的事情说三遍)因为需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这必然会带来较大的不便,在这个角度,它违背了开闭(对扩展开放,对修改封闭)原则。

  想想,如果我们需要为单选按钮(RadioButton)提供不同皮肤的风格化显示,会发现无论选择哪种皮肤,单选按钮都显得“格格不入”。

4.3 抽象工厂模式应用场景

  (1)用户无须关心对象的创建过程,需要将对象的创建和使用解耦 -> 这是所有工厂模式的使用前提

  (2)系统中有多余一个的产品族,而每次都只使用其中的某一种产品族。 -> 可以通过配置文件等方式来使得用户可以动态地改变产品族,也可以很方便地增加新的产品族

  (3)产品等级结构稳定!设计完成之后,不会向系统中增加新的产品等级结构或删除已有产品等级结构。 -> 并不太符合开闭原则

参考资料

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—3.抽象工厂(Abstract Factory)模式的更多相关文章

  1. 面向对象设计——抽象工厂(Abstract Factory)模式

    定义 提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类.抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道或关心实际产出的具体产品是什么.这样一来,客户就能从具体的产 ...

  2. Java 工厂模式(一)— 抽象工厂(Abstract Factory)模式

    一.抽象工厂模式介绍: 1.什么是抽象工厂模式: 抽象工厂模式是所有形态的工厂模式中最为抽象和最具有一般性的一种形态,抽象工厂模式向客户端提供一个接口,使得客户端在不知道具体产品的情类型的情况下,创建 ...

  3. Headfirst设计模式的C++实现——抽象工厂(Abstract Factory)

    Dough.h #ifndef _DOUGH_H #define _DOUGH_H class Dough { }; #endif ThinCrustDough.h #ifndef _THIN_CRU ...

  4. 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)

    在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...

  5. 5、抽象工厂 abstract factory 将关联组件组成产品 创建型模式

    趁热打铁,紧跟着上一节的工厂方法模式.这一节介绍一下抽象工厂模式,以及分析俩个模式的不同 1.何为抽象模式? 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他 ...

  6. 设计模式的征途—4.抽象工厂(Abstract Factory)模式

    上一篇的工厂方法模式引入了工厂等级结构,解决了在原来简单工厂模式中工厂类职责太重的原则,但是由于工厂方法模式的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,从而增加系统开销.那么,我们应该 ...

  7. 设计模式——抽象工厂(Abstract Factory)

    提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类. ——DP UML类图 模式说明 抽象工厂与工厂方法在定义上最明显的区别是“创建一系列相关或相互依赖对象的接口”,由此可以看出抽象工 ...

  8. 设计模式四: 抽象工厂(Abstract Factory)

    简介 抽象工厂模式是创建型模式的一种, 与工厂方法不同的是抽象工厂针对的是生产一组相关的产品, 即一个产品族. 抽象工厂使用工厂方法模式来生产单一产品, 单一产品的具体实现分别属于不同的产品族. 抽象 ...

  9. 【设计模式】——抽象工厂Abstract Factory

    模式意图 提供对象的使用接口,隐藏对象的创建过程. 模式结构 AbstractFactory 提供创建对象的接口. ConcreteFactory 提供真正创建对象的实现类,用于组合并创建不同的对象, ...

随机推荐

  1. @Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。

    @Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationEx ...

  2. Python中参数是传值,还是传引用?

    在 C/C++ 中,传值和传引用是函数参数传递的两种方式,在Python中参数是如何传递的?回答这个问题前,不如先来看两段代码. 代码段1: def foo(arg): arg = 2 print(a ...

  3. JavaWeb之Ajax

    一.什么是Ajax 1.1.Ajax的定义 Ajax:(Asynchronous JavaScript And XML)指异步 JavaScript 及 XML 不是一种新的编程语言,而是一种用于创建 ...

  4. CoreAnimation 视觉效果

    CoreAnimation 视觉效果 CoreAnimation 目录 博客园MakeDown支持不佳,如有需要请进GitHub iPhone手机的视觉效果是十分优秀的,因此作为iOS工程师一定要对其 ...

  5. 十分钟搭建redis单机版 & java接口调用

    本次单机版redis服务器搭建采用的包为redis-3.0.0.tar.gz,主要是记录下安装的心得,不喜勿喷! 一.搭建redis服务器单机版 1.上传redis-3.0.0.tar.gz到服务器上 ...

  6. 写入soap消息以及与soap消息通信

    1.些了解一下soap消息的结构以及通信方式如下图:

  7. iOS开发之类扩展

    在以往写代码时,我们经常是把声明写在.h文件中,把实现写在.m文件中,但是在实际开发中,如果把声明写在.h文件中会暴露程序很多属性(成员变量.成员变量的get和set方法),为了安全考虑,引入了类扩展 ...

  8. 初识 Javascript.02 -- Date日期、Math对象、数据类型转换、字符串、布尔Boolean、逻辑运算符、if else 、三元表达式、代码调试方法、

    Date()对象: Date对象用于处理日期和时间. 1.1 Math对象  ◆Math.ceil()   天花板函数    向上取整  只取整数,不足则进1 ◆Math.floor()  地板函数 ...

  9. TFS发布计划发送到钉钉消息群

    由于工作中需要用到钉钉,每天都要和钉钉打交道:上下班打卡.出差请假流程.各种工作讨论组,不一而足,工作已然和钉钉绑在了一起,难怪有广告词: 微信是一个生活方式,钉钉是一个工作方式. 我们是钉钉机器人内 ...

  10. 自定义template

    今天写代码写的有点烦了,感觉天天写new String(); new HashMap<String,String>()等,感觉写烦了,有没有快速的方法了.就你输入syso然后按atl+/就 ...