上一篇的工厂方法模式引入了工厂等级结构,解决了在原来简单工厂模式中工厂类职责过重的原则,可是因为工厂方法模式的每一个工厂只生产一类产品,可能会致使系统中存在大量的工厂类,从而增长系统开销。那么,咱们应该怎么来重构?彷佛,咱们能够考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一辈子产,这就是本次将要学习的抽象工厂模式的基本思想。编程
抽象工厂模式(Abstract Factory) | 学习难度:★★★★☆ | 使用频率:★★★★★ |
M公司IT开发部接到一个开发任务,想要对之前的一个系统开发一套界面皮肤库,能够对该桌面系统软件进行界面美化。这样,用户就能够在使用时经过菜单来选择皮肤,不一样的皮肤将提供视觉效果不一样的按钮、文本框以及组合框等界面元素,其结构示意图以下所示:设计模式
该皮肤库须要具有良好的灵活性和可扩展性,用户能够自由选择不一样的皮肤,开发人员也能够在不修改既有代码的基础上增长新的皮肤。app
M公司的开发人员针对上述需求,决定现学现卖,在上次使用了工厂方法模式以后对工厂方法模式大为赞扬,决定使用工厂方法模式来进行系统的设计,为了保证系统的灵活性和可扩展性,提供一系列具体工厂来建立按钮、文本框以及组合框等界面元素,客户端针对抽象工厂来编程,初始结构以下图所示:ide
从上图能够看出,此方案提供了大量的工厂来建立具体的界面组件,能够经过配置文件来更换具体界面组件从而改变界面的风格,可是,此方案存在如下问题:学习
(1)须要增长新的皮肤时,虽然不须要更改现有代码,可是须要增长大量的类,针对每个新增具体组件都要增长一个具体工厂,类的个数会成对增长。这无疑会致使系统愈来愈庞大,从而增长了系统的维护成本和运行开销。测试
(2)因为同一种风格的不一样界面组件一般须要一块儿显示,所以须要为每一个组件都选择一个具体工厂,用户在使用时必须逐个进行设置,若是某个具体工厂选择失误将会致使界面显示混乱,虽然能够适当增长一些约束语句,可是客户端代码和配置文件都较为复杂。spa
综上所述,如何减小系统中类的个数并保证客户端每次始终就只使用一种风格的具体界面组件?这是萦绕在M公司开发人员心头的两个问题。设计
(1)产品等级3d
产品等级即产品的继承结构,例如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌电视机则是子类。调试
(2)产品族
产品族是指由同一个工厂生产的,位于不一样产品等级结构中的一组,例如海尔电器厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,而海尔电冰箱则位于电冰箱产品等级结构中,他们俩构成了一个产品族。
产品等级与产品族的示意图以下图所示:
能够看出,当系统所提供的工厂生产的具体产品并非一个简单的对象,而是多个位于不一样产品等级结构、属于不一样类型的具体产品时,就可使用抽象工厂模式。抽象工厂模式是全部形式的工厂模式中最为抽象和最具通常性的一种形式。
Note :抽象工厂与工厂方法最大的区别就在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式须要面对多个产品等级结构,一个工厂等级结构能够负责多个不一样产品等级中的产品对象的建立。
抽象工厂模式为建立一组对象提供了一种方案,与工厂方法模式相比,抽象工厂模式中的具体工厂不仅是建立一种产品,它负责建立一族产品。其定义以下:
Definition :抽象工厂模式提供一个建立一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象建立型模式。
抽象工厂模式的结构图以下图所示:
(1)Abstract Factory (抽象工厂角色):声明了一组用于建立一族产品的方法,每个方法对应一种产品。
(2)Concrete Factory (具体工厂角色):实现了在抽象工厂中声明的建立产品的方法,生成一组具体产品,这些产品构成了一个产品族。
(3)Abstract Product (抽象产品角色):为每种产品声明接口,在抽象产品中声明了全部的业务方法。
(4)Concrete Product (具体产品角色):定义具体工厂生产的具体产品对象,实如今抽象产品接口中声明的业务方法。
M公司使用抽象工厂模式来重构了界面皮肤库的设计,其基本结构以下图所示:
(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(); } }
(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"/>
再次运行程序,结果是咱们想要的:
(1)隔离了具体类的生成,使得客户并不须要知道什么被建立。由于这种隔离,所以更换一个具体工厂就变得相对容易。
(2)当一个产品族中的多个对象被设计称一块儿工做时,它可以保证客户端始终只使用同一个产品族中的对象。
(3)增长新的产品族很方便,无需修改已有系统,符合开闭原则。
增长新的产品等级结构很麻烦,增长新的产品等级结构很麻烦,增长新的产品等级结构很麻烦!!!(重要的事情说三遍)由于须要对原有系统进行较大的修改,甚至须要修改抽象层代码,这必然会带来较大的不便,在这个角度,它违背了开闭(对扩展开放,对修改封闭)原则。
想一想,若是咱们须要为单选按钮(RadioButton)提供不一样皮肤的风格化显示,会发现不管选择哪一种皮肤,单选按钮都显得“格格不入”。
(1)用户无须关心对象的建立过程,须要将对象的建立和使用解耦 -> 这是全部工厂模式的使用前提
(2)系统中有多余一个的产品族,而每次都只使用其中的某一种产品族。 -> 能够经过配置文件等方式来使得用户能够动态地改变产品族,也能够很方便地增长新的产品族
(3)产品等级结构稳定!设计完成以后,不会向系统中增长新的产品等级结构或删除已有产品等级结构。 -> 并不太符合开闭原则
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》