在工厂方法模式中通过引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题。但是由于工厂方法模式中每个工厂只生产一类产品,这样可能会导致系统中存在大量的工厂类,势必会增加系统的开销。
为了解决这个问题,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。
A科技公司打算开发一套界面皮肤库,可以对Winform桌面软件进行界面美化。用户可以通过菜单来选择皮肤,不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素。结构示意图如下:
该皮肤库需要具备良好的灵活性和可扩展性,用户可以自由的选择不同的皮肤,开发人员可以在不修改既有代码的基础上增加新的皮肤。
A科技公司的开发人员基于上述要求,决定使用工厂方法模式来进行系统的设计,为了保证系统的灵活性和可扩展性,提供了一系列具体工厂来创建按钮、文本框、组合框等界面元素,客户端针对抽象工厂编程。初始结构如图:
上图中提供了大量的工厂来创建具体的界面组件,可以通过配置文件更换具体界面组件从而改变风格。但是这个设计方案存在以下几个问题:
怎么既能减少系统中类的个数,又能保证客户端每次始终只使用某一种风格的具体界面组件呢?很显然工厂方法模式无法解决这个问题。而抽象工厂模式就可以让这些问题迎刃而解。
工厂方法模式中,具体工厂负责生产具体的产品,每个具体工厂都对应一个具体产品。但有时,我们希望一个工厂可以生产多个产品对象。比如,一个电器工厂,它可以生产手机、平板、耳机等多种产品。为了更好的立即抽象工厂模式,这里引入两个概念:
产品等级结构和产品族示意图如下:
上图一共3个产品族(小米、华为、苹果),分属于3个不同产品等级结构(手机、电脑、平板)。只要知道一个产品的产品族和产品等级结构就可以确定这个产品。(坐标的概念)
如果一个工厂生产的具体产品不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时,就可以使用抽象工厂模式。
每个具体的工厂都可以生产属于一个产品族的所有产品,比如小米的工厂可以生产小米手机、小米电脑、小米平板等产品,每个产品又位于不同的产品等级结构中。这样上图中如果使用工厂方法模式,则需要为每个产品都提供一个具体的工厂,这就要创建9个工厂类。但如果使用抽象工厂模式,我们只需要为三个产品族创建3个工厂类即可,这样就极大的减少了工厂类的个数。
抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式的具体工厂不只是创建一种产品,而是负责创建一族产品。定义如下:
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或互相依赖对象的接口,而无须指定它们的具体的类。抽象工厂模式又称 Kit 模式,它是一种对象创建型模式。
抽象工厂模式中,每个具体的工厂都提供了多个工厂方法用于生产多种不同类型的产品,这些产品构成了一个产品族。比如:小米工厂有多个产品线,可以生产多种类型的不同小米产品,这些不同类型的产品组成了小米产品家族。抽象工厂模式的结构图如下:
抽象工厂模式结构图中包含4个角色:
在抽象工厂中声明了多个工厂方法,用于创建不同类型的产品,抽象工厂可以是接口,也可以是抽象类或具体类。典型代码如下:
public interface AbstractFactory{
AbstractProductA CreateProductA();//工厂方法一
AbStractProductB CreateProductB();//工厂方法二
}
具体工厂则实现了抽象工厂,每个具体的工厂方法可以返回一个特定的产品对象,且同一个具体工厂所创建的产品对象构成了一个产品族。
public class ConcreteFactory1 : AbstractFactory{
//工厂方法一
public AbstractProductA CreateProductA(){
return new ConcreteProductA1();
}
//工厂方法二
public AbstractProductB CreateProductB(){
return new ConcreteProductB1();
}
}
开发人员使用抽象工厂模式来重构界面皮肤库的设计,结构图如下:
/// <summary>
/// 按钮接口:抽象产品
/// </summary>
public interface Button
{
void Display();
}
/// <summary>
/// Spring按钮类:具体产品
/// </summary>
public class SpringButton : Button
{
public void Display()
{
Console.WriteLine("显示Spring风格按钮");
}
}
/// <summary>
/// Summer按钮类:具体产品
/// </summary>
public class SummerButton : Button
{
public void Display()
{
Console.WriteLine("显示Summer风格按钮");
}
}
/// <summary>
/// 文本框接口:抽象产品
/// </summary>
public interface TextField
{
void Display();
}
/// <summary>
/// Spring 文本框类:具体产品
/// </summary>
public class SpringTextField : TextField
{
public void Display()
{
Console.WriteLine("显示Spring风格文本框");
}
}
/// <summary>
/// Summer 文本框类:具体产品
/// </summary>
public class SummerTextField : TextField
{
public void Display()
{
Console.WriteLine("显示Summer风格文本框");
}
}
/// <summary>
/// 组合框接口:抽象产品
/// </summary>
public interface ComboBox
{
void Display();
}
/// <summary>
/// Spring组合框类:具体产品
/// </summary>
public class SpringComboBox : ComboBox
{
public void Display()
{
Console.WriteLine("显示Spring风格组合框");
}
}
/// <summary>
/// Summer 组合框类:具体产品
/// </summary>
public class SummerComboBox : ComboBox
{
public void Display()
{
Console.WriteLine("显示Summer风格组合框");
}
}
/// <summary>
/// 界面皮肤工厂接口:抽象工厂
/// </summary>
public interface ISkinFactory
{
Button CreateButton();
TextField CreateTextField();
ComboBox CreateComboBox();
}
/// <summary>
/// Spring 皮肤工厂:具体工厂
/// </summary>
public class SpringSkinFactory : ISkinFactory
{
public Button CreateButton()
{
return new SpringButton();
}
public TextField CreateTextField()
{
return new SpringTextField();
}
public ComboBox CreateComboBox()
{
return new SpringComboBox();
}
}
/// <summary>
/// Summer 皮肤工厂:具体皮肤工厂
/// </summary>
public class SummerSkinFactory : ISkinFactory
{
public Button CreateButton()
{
return new SummerButton();
}
public TextField CreateTextField()
{
return new SummerTextField();
}
public ComboBox CreateComboBox()
{
return new SummerComboBox();
}
}
为了让系统更具备良好的灵活性和可扩展性,这里依旧将具体工厂添加到配置文件中,并添加配置文件的帮助类,具体代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--value为具体工厂类的完全限定名(命名空间+类名)-->
<add key="SkinFactory" value="LXP.DesignPattern.AbstractFactory.SpringSkinFactory"/>
</appSettings>
</configuration>
帮助类代码:
public class AppConfigHelper
{
/// <summary>
/// 获取具体皮肤工厂方法
/// </summary>
/// <returns></returns>
public static object GetSkinFactory()
{
try
{
var skinFactoryName = ConfigurationManager.AppSettings["SkinFactory"];
var type = Type.GetType(skinFactoryName);
return type == null ? null : Activator.CreateInstance(type);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
}
客户端测试代码:
class Program
{
static void Main(string[] args)
{
var skinFactory = (ISkinFactory) AppConfigHelper.GetSkinFactory();
var button = skinFactory.CreateButton();
var textField = skinFactory.CreateTextField();
var comboBox = skinFactory.CreateComboBox();
button.Display();
textField.Display();
comboBox.Display();
}
}
输出结果:
上述实现,如果以后要更换皮肤,只需要修改配置文件即可。事实上,在实际工作中,通常会提供一个可视化界面来管理配置,这样用户如果要更换皮肤只需要在配置界面配置即可,无须手动修改配置文件,更加的方便。如果要增加新的皮肤,只需要添加一族新的具体的组件并对应提供一个具体的工厂,修改配置文件即可,无须修改代码,符合开闭原则。
上述实现可以较为方便的增加新的皮肤,但是也存在一个非常严重的问题:由于设计时只针对Button、TextField、ComboBox提供了不同风格化的显示,忘记为单选按钮(RadioButton)提供,那么无论选择那种皮肤,单选按钮都不会变化,在界面上就会显得“格格不入”。开发人员决定往系统中增加单选按钮,但是发现原有的系统不能在符合开闭原则的情况下添加新的组件,原因是抽象工厂 ISkinFactory 中没有提供创建单选按钮的方法,如果要增加单选按钮,首先要修改抽象工厂接口,添加创建单选按钮的方法,然后在不同风格的具体工厂类中实现创建单选按钮的方法,然后还要修改客户端等一系列操作。
这也是抽象工厂模式最大的缺点。在抽象工厂模式中增加新的产品族很方便,但是增加新的产品等级结构就很麻烦,抽象工厂模式的这种性质称为开闭原则的倾斜性(倾向于产品族的扩展)。开闭原则要求系统对扩展开放,对修改关闭,通过扩展达到增强其功能的目的,对于涉及多个产品族和多个产品等级结构的系统,其功能增强包括两个方面:
正是因为抽象工厂模式存在的开闭原则的倾斜性,它以一种倾斜的方式来满足开闭原则,为增加新的产品族提供方便,无法为增加新的产品等级结构提供方便。这种就要求设计人员在设计之初就能够全面考虑,不会在设计完成以后向系统中添加新的产品等级结构,也不会删除系统中已有的产品等级结构,不然系统将会出现大范围的修改,为后续的维护工作带来诸多麻烦。
抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了较为强大的工厂类并且具备更好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和 API 类库的设计中。
抽象工厂模式的主要缺点就是:增加新的产品等级结构很麻烦,需要对原有的系统进行大范围的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则。
如果您觉得这篇文章有帮助到你,欢迎推荐,也欢迎关注我的公众号。
https://github.com/crazyliuxp/DesignPattern.Simples.CSharp
原文:https://www.cnblogs.com/imlxp/p/14496745.html