二十三种设计模式[2] - 抽象工厂(Abstract Factory)
前言
抽象工厂又称Kit,创建型模式的一种。在《设计模式 - 可复用的面向对象软件》一书中对它的描述为“ 提供一个创建一系列相关或相互依赖的对象的接口,而无需指定它们具体的类 ”。
个人将抽象工厂理解为“ 一个承载了多个工厂方法的超级工厂,每个超级工厂负责生产一个产品族 ”。ps:产品族是以产品平台为基础,通过添加不同的个性模块,以满足不同客户个性化需求的一组相关产品。比如,罗技品牌下的所有产品称之为罗技的产品族。
在学习抽象工厂之前,最好熟悉工厂方法。可参考二十三种设计模式[1] - 工厂方法(Factory Method)。
结构
需要角色如下:
- IProduct(产品接口):工厂方法生产的产品接口;
- Product(产品):产品接口的实现类;
- IFactory(工厂接口):承载了多个工厂方法的接口;
- ConcreteFactory(工厂):工厂接口的实现类;
实现
以罗技和雷蛇两个品牌为例。
罗技的产品族包括鼠标、键盘等等。雷蛇的产品族也同样包括鼠标和键盘。我们先为这两个品牌的产品抽象出对应的接口以及接口的实现,在根据两个品牌的产品族抽象出一个工厂接口以及接口的实现,如下。
public interface IMouse { string GetBrand(); } public class LogitechMouse : IMouse { public string GetBrand() { return "罗技-Logitech G903"; } } public class RazeMouse : IMouse { public string GetBrand() { return "雷蛇-Raze 蝰蛇"; } } public interface IKeyBoard { string GetBrand(); } public class LogitechKeyboard : IKeyBoard { public string GetBrand() { return "罗技-Logitech G910 RGB"; } } public class RazeKeyboard : IKeyBoard { public string GetBrand() { return "雷蛇-Raze 萨诺狼蛛"; } } public interface IFactory { IMouse CreateMouse(); IKeyBoard CreateKeyboard(); } public class LogitechFactory : IFactory { public IKeyBoard CreateKeyboard() { return new LogitechKeyboard(); } public IMouse CreateMouse() { return new LogitechMouse(); } } public class RazeFactory : IFactory { public IKeyBoard CreateKeyboard() { return new RazeKeyboard(); } public IMouse CreateMouse() { return new RazeMouse(); } } class Program { static void Main(string[] args) { //创建抽象工厂 IFactory factory = new LogitechFactory(); //IFactory factory = new RazeFactory(); //通过工厂生产产品实体 IMouse mouse = factory.CreateMouse(); IKeyBoard keyboard = factory.CreateKeyboard(); Console.WriteLine($"当前工厂生产的鼠标是:{mouse.GetBrand()}"); Console.WriteLine($"当前工厂生产的键盘是:{keyboard.GetBrand()}"); Console.ReadKey(); } }
示例中就是一个简单的抽象工厂,调用者通过IFactory的子类去实例化IFactory接口本身(多态,面向对象的基本特征之一),再调用相应的工厂方法去实例化相应的产品实体。达到让子类决定实例化哪一些类,使一些类的实例化延迟到子类的目的。
抽象工厂最大的好处在于,我们只需要修改实例化的产品族工厂就可以将产品族内的一系列产品全部修改。
在本示例的基础上,如果增加了品牌时,我们需要增加实现了IMouse和IKeyboard接口的产品类,以及负责生产该品牌产品族的工厂即可。
但当我们增加产品族内的产品时,抽象工厂的弊端就显现出来了,它的改动量是相当可怕的。比如下图中,我们增加一个产品 “耳机” 就需要在增加产品接口和实现类的同时修改工厂接口及其所有实现类,可谓是 “牵一发而动全身” 。
为了解决这个问题,我想到了工厂方法中的简单工厂,摒弃所有的工厂类只保留一个简单工厂,简单工厂通过函数入参或者本地配置确定工厂将要创建的产品族,在通过反射来获取具体的产品。但反射只适用于所有同类型产品的实例化逻辑一致的情况下(即所有IMouse接口的实现类的实例化逻辑一致)。所以,我们折中一下,在原有的抽象工厂基础上增加一个简单工厂吧。
增加本地配置
<appSettings> <add key="MouseSetting" value="DesignPatternsPractice.LogitechMouse" /> <add key="KeyboardSetting" value="DesignPatternsPractice.RazeKeyboard" /> </appSettings>
增加简单工厂CommonFactory
public class CommonFactory : IFactory { private Type _mouseType = null; private Type MouseType { get { if(this._mouseType == null) { var fullName = System.Configuration.ConfigurationManager.AppSettings["MouseSetting"]; this._mouseType = string.IsNullOrWhiteSpace(fullName) ? null : Type.GetType(fullName); } return this._mouseType; } } private Type _keyboardType = null; private Type KeyboardType { get { if(this._keyboardType == null) { var fullName = System.Configuration.ConfigurationManager.AppSettings["KeyboardSetting"]; this._keyboardType = string.IsNullOrWhiteSpace(fullName) ? null : Type.GetType(fullName); } return this._keyboardType; } } public IKeyBoard CreateKeyboard() { return Activator.CreateInstance(this.KeyboardType) as IKeyBoard; } public IMouse CreateMouse() { return Activator.CreateInstance(this.MouseType) as IMouse; } }
Main函数
static void Main(string[] args) { //创建抽象工厂 IFactory factory = new CommonFactory(); //通过工厂生产产品实体 IMouse mouse = factory.CreateMouse(); IKeyBoard keyboard = factory.CreateKeyboard(); Console.WriteLine($"当前工厂生产的鼠标是:{mouse.GetBrand()}"); Console.WriteLine($"当前工厂生产的键盘是:{keyboard.GetBrand()}"); Console.ReadKey(); }
将部分实例化逻辑一致的产品通过CommonFactory来获取,其他产品则通过特有工厂来获取,即保证了它的灵活性,又能减少部分因增加产品族内的产品而造成工厂的改动。例子中,简单工厂CommonFactory只是读取特定的配置,你也可以通过构造函数或者工厂方法的入参来设置读取的配置文件。这里就不一一叙述了。
总结
抽象工厂,一个承载了多个工厂方法的超级工厂。当我们需要根据不同的业务场景对一系列产品进行改动时,只需要修改对应的工厂即可达到产品系列切换的目的(比如不同DB间切换,应用程序皮肤的切换,不同接口方式的切换)。
抽象工厂并不适用于产品基数较少的情况。
在增加产品族内产品时,需要对工厂接口及其所有实现类做出相应改动。接口的实现类越多,需要做出的改动也就越多。
设计模式本身不是一套固定的代码,更多的是一种设计理念。它不是完美的,只是在特定问题、特定需求下是最好的选择。
以上,就是我对抽象工厂的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078558.html)