抽象工厂模式(Abstract Factory Pattern)比工厂方法模式具有更高层次的抽象。当要返回一系列相关类中的某一个,而每个类都能根据不同需要返回不同类时,可以使用这种模式。
生活中的例子:
假设一农场主要设计一个蔬菜型花园(VeggieGarden) ,如果这个花园只需要考虑三个关键地方,即:
1、边缘种什么植物(borderPlant)。
2、中央种什么植物(centerPlant)。
3、阴凉部分种什么植物(shadePlant)。
我们的代码可以这样:
{
public void Draw();
}
public class VeggieCenter
{
public void Draw();
}
public class VeggieShade
{
public void Draw();
}
客户端使用
在通常情况下这么写没有任何问题,如果该农场发展壮大,扩建一块水果型花园(FruitGarden),这时我们需要为水果型花园编写和上面类似代码。考虑到每种植物都有公共方法Draw,我们可以抽象出一个公共接口类出来Plant,这时代码可以这么写
{
void Draw();
}
publicclass FruitBorder : Plant
{
public void Draw();
}
public class FruitCenter : Plant
{
public void Draw();
}
public class FruitShade : Plant
{
public void Draw();
}
//蔬菜型花园
{
public void Draw();
}
public class VeggieCenter : Plant
{
public void Draw();
}
public class VeggieShade : Plant
{
public void Draw();
}
客户端调用代码
Plant border = new FruitBorder();
border.Draw();
Plant center = new FruitCenter();
center.Draw();
Plant shade = new FruitShade();
shade.Draw();
//蔬菜型花园调用方法
Plant border = new VeggieBorder();
border.Draw();
Plant center = new VeggieCenter();
center.Draw();
Plant shade = new VeggieShade();
shade.Draw();
这时发现使用起来一点都不方面,虽然提取了一个接口,但工作量没有少,如果以后需要在蔬菜型花园和水果型花园直接转变就需要改动大量代码,这是我们不愿看到的。添加一个工厂就非常有必要了。工厂类代码如下:
{
public Plant CreateBorder()
{
return new FruitBorder(); //关键部分
}
public Plant CreateCenter()
{
return new FruitCenter(); ////关键部分 }
public Plant CreateShade()
{
return new FruitShade(); //关键部分
}
}
客户端调用
border.Draw();
Plant center = new FruitFactory().CreateCenter();
center.Draw();
Plant shade = new FruitFactory().CreateShade();
shade.Draw();
恩,不错。大家都明白了,如果需要变动,只需要改动工厂类的关键部分就行。但是,前面的解决方案带来了一个副作用:就是系统不但增加了新的类Factory,而且当系统移植时,工作量并没有任何缩减,还是要修改系统的源码,没有增加系统灵活性。
OK,我们继续修改解决方案,增加一个抽象工厂类AbstractFactory,增加一个静态方法,该方法根据一个配置文件(App.config或者Web.config) 一个项动态地判断应该实例化哪个工厂类,这样,我们就把移植工作转移到了对配置文件的修改。修改后的代码:
{
static string pixeName = ConfigSeting.AppWeb.ToString(); //从配置文件中获取
public static Plant CreateBorder()
{
string name = pixeName + "Border";
Plant instance = (Plant)Assembly.Load(name).CreateInstance(name);
return instance;
}
public static Plant CreateCenter()
{
string name = pixeName + "Center";
Plant instance = (Plant)Assembly.Load(name).CreateInstance(name);
return instance;
}
public static Plant CreateShade()
{
string name = pixeName + "Shade";
Plant instance = (Plant)Assembly.Load(name).CreateInstance(name);
return instance;
}
}
客户端调用方法没有改变,但系统灵活性却大大增加。下面引用TerryLee的一段话,总结抽象工厂方法特点.
应对“新对象”
抽象工厂模式主要在于应对“新系列”的需求变化。其缺点在于难于应付“新对象”的需求变动。如果在开发中出现了新对象,该如何去解决呢?这个问题并没有一个好的答案,下面我们看一下李建忠老师的回答:
“GOF《设计模式》中提出过一种解决方法,即给创建对象的操作增加参数,但这种做法并不能令人满意。事实上,对于新系列加新对象,就我所知,目前还没有完美的做法,只有一些演化的思路,这种变化实在是太剧烈了,因为系统对于新的对象是完全陌生的。”
实现要点
l 抽象工厂将产品对象的创建延迟到它的具体工厂的子类。
l 如果没有应对“多系列对象创建”的需求变化,则没有必要使用抽象工厂模式,这时候使用简单的静态工厂完全可以。
l 系列对象指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中的“道路”与“房屋”的依赖,“道路”与“地道”的依赖。
l 抽象工厂模式经常和工厂方法模式共同组合来应对“对象创建”的需求变化。
l 通常在运行时刻创建一个具体工厂类的实例,这一具体工厂的创建具有特定实现的产品对象,为创建不同的产品对象,客户应使用不同的具体工厂。
l 把工厂作为单件,一个应用中一般每个产品系列只需一个具体工厂的实例,因此,工厂通常最好实现为一个单件模式。
l 创 建产品,抽象工厂仅声明一个创建产品的接口,真正创建产品是由具体产品类创建的,最通常的一个办法是为每一个产品定义一个工厂方法,一个具体的工厂将为每 个产品重定义该工厂方法以指定产品,虽然这样的实现很简单,但它确要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。
优点
l 分离了具体的类。抽象工厂模式帮助你控制一个应用创建的对象的类,因为一个工厂封装创建产品对象的责任和过程。它将客户和类的实现分离,客户通过他们的抽象接口操纵实例,产品的类名也在具体工厂的实现中被分离,它们不出现在客户代码中。
l 它使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。
l 它有利于产品的一致性。当一个系列的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要,而抽象工厂很容易实现这一点。
缺点
l 难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几口确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。
适用性
在以下情况下应当考虑使用抽象工厂模式:
l 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
l 这个系统有多于一个的产品族,而系统只消费其中某一产品族。
l 同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
l 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
应用场景
l 支持多种观感标准的用户界面工具箱(Kit)。
l 游戏开发中的多风格系列场景,比如道路,房屋,管道等。
l ……工厂方法模式和抽象工厂模式的区别:
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。