在工厂方法模式中,我们使用一个工厂创建一个产品,也就是说一个具体的工厂对应一个具体的产品。但是有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
在讲解抽象工厂模式之前,我们需要厘清两个概念
产品等级结构:产品的等级结构也就是产品的继承结构。例如一个为空调的抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个抽象类空调和他的子类就构成了一个产品等级结构。
产品族:产品族是在抽象工厂模式中的。在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调。海尔冰箱,那么海尔空调则位于空调产品族中。
产品等级结构和产品族结构示意图如下:
1、基本定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。
2、UML类图
在抽象工厂模式结构图中包含如下几个角色:
在抽象工厂模式结构图中包含如下几个角色:
● IFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
● Factory1/Factory2(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
● IProductA/IProductB(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
● ProductA1/ProductA2/ProductB1/ProductB2(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。
在抽象工厂中声明了多个工厂方法,用于创建不同类型的产品,抽象工厂可以是接口,也可以是抽象类或者具体类
3、模式实现
我们就是现实中的例子来实现抽象工厂,海尔生产冰箱、洗衣机。美的也生产冰箱洗衣机,设计图如下:
代码实现
产品类代码如下:
/// <summary>
/// 冰箱
/// </summary>
public interface IFridge
{
void CreateFridge();
}
/// <summary>
/// 海尔冰箱
/// </summary>
public class HaierFridge : IFridge
{
public void CreateFridge()
{
Console.WriteLine("生产了一台海尔冰箱");
}
}
/// <summary>
/// 美的冰箱
/// </summary>
public class MideaFridge:IFridge
{
public void CreateFridge()
{
Console.WriteLine("生产了一台美的冰箱");
}
}
/// <summary>
/// 洗衣机
/// </summary>
public interface IWasher
{
void CreateWasher();
}
/// <summary>
/// 海尔洗衣机
/// </summary>
public class HaierWasher:IWasher
{
public void CreateWasher()
{
Console.WriteLine("生产了一台海尔洗衣机");
}
}
/// <summary>
/// 美的洗衣机
/// </summary>
public class MideaWasher:IWasher
{
public void CreateWasher()
{
Console.WriteLine("生产了一台美的洗衣机");
}
}
接下来我们来看工厂类,代码如下:
/// <summary>
/// 家用电器工厂类
/// </summary>
public interface IFactory
{
IFridge CreateFridgeInstance();
IWasher CreateWasherInstance();
}
/// <summary>
/// 海尔工厂类
/// </summary>
public class HaierFactory:IFactory
{
public IFridge CreateFridgeInstance()
{
return new HaierFridge();
}
public IWasher CreateWasherInstance()
{
return new HaierWasher();
}
}
/// <summary>
/// 美的工厂类
/// </summary>
public class MideaFactory:IFactory
{
public IFridge CreateFridgeInstance()
{
return new MideaFridge();
}
public IWasher CreateWasherInstance()
{
return new MideaWasher();
}
}
调用代码如下:
class Program
{
static void Main(string[] args)
{
IFactory factory = new HaierFactory();//调用海尔工厂类
IFridge fridge = factory.CreateFridgeInstance();
fridge.CreateFridge();//生产冰箱
IWasher washer = factory.CreateWasherInstance();
washer.CreateWasher();//生产洗衣机
factory = new MideaFactory();//调用美的工厂类
fridge = factory.CreateFridgeInstance();
fridge.CreateFridge();//生产冰箱
washer = factory.CreateWasherInstance();
washer.CreateWasher();//生产洗衣机
}
}
4、“开闭原则”的倾斜性
在上面的示例中,我们可以较为方面的添加新工厂,如容声也生产冰箱与洗衣机,但是现在遇到一个非常严重的问题,就是如果我们想添加空调类时,发现原有系统居然不能够在符合“开闭原则”的前提下增加新的组件,原因是抽象工厂IFactory中根本没有提供创建空调实例的方法,如果需要增加空调,首先需要修改抽象工厂接口IFactory,在其中新增声明创建空调对象的方法,然后逐个修改具体工厂类,增加相应方法以实现不同的生产厂家都可以创建空调对象,此外还需要修改客户端,否则空调类无法应用于现有系统。
怎么办?答案是抽象工厂模式无法解决该问题,这也是抽象工厂模式最大的缺点。在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为“开闭原则”的倾斜性。“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:
(1) 增加产品族:对于增加新的产品族,抽象工厂模式很好地支持了“开闭原则”,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改。
(2) 增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。
正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。
上面扩展容声容声代码,添加三个类、容声冰箱类,继承自冰箱接口;容声洗衣机类,继承自洗衣机里,容声工厂类,继承自工厂类(包含创建冰箱实例与洗衣机实例),代码如下:
/// <summary>
/// 容声冰箱
/// </summary>
public class RonShenFridge:IFridge
{
public void CreateFridge()
{
Console.WriteLine("生产了一台容声冰箱");
}
}
/// <summary>
/// 容声洗衣机
/// </summary>
public class RonShenWasher:IWasher
{
public void CreateWasher()
{
Console.WriteLine("生产了一台容声洗衣机");
}
}
/// <summary>
/// 容声工厂类
/// </summary>
public class RonShenFactory:IFactory
{
//创建冰箱实例
public IFridge CreateFridgeInstance()
{
return new RonShenFridge();
}
//创建洗衣机实例
public IWasher CreateWasherInstance()
{
return new RonShenWasher();
}
}
调用方法
class Program
{
static void Main(string[] args)
{
IFactory factory = new HaierFactory();//调用海尔工厂类
IFridge fridge = factory.CreateFridgeInstance();
fridge.CreateFridge();//生产冰箱
IWasher washer = factory.CreateWasherInstance();
washer.CreateWasher();//生产洗衣机
factory = new RonShenFactory();//调用容声工厂类
fridge = factory.CreateFridgeInstance();
fridge.CreateFridge();//生产冰箱
washer = factory.CreateWasherInstance();
washer.CreateWasher();//生产洗衣机
}
}
5、抽象工厂模式优缺点
抽象工厂模式的主要优点如下:
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
抽象工厂模式的主要缺点如下:
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
6、模式使用场景
在以下情况下可以考虑使用抽象工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
7、总结
抽象工厂模式中主要的优点在于具体类的隔离,是的客户端不需要知道什么被创建了。其缺点在于增加新的等级产品结构比较复杂,需要修改接口及其所有子类。