抽象工厂(Abstract Factory)模式

文章目录

  • 抽象工厂(Abstract Factory)模式
    • 1. 意图
    • 2. 别名
    • 3. 动机
    • 4. 适用性
    • 5. 结构
    • 6. 参与者
    • 7. 协作
    • 8. 效果
    • 9. 实现
    • 10. 代码示例
    • 11. 已知应用
    • 12. 相关模式
    • 13. 设计原则口袋
    • 14. 参考文献

抽象工厂(Abstract Factory)模式

隶属类别——对象创建型模式

1. 意图

提供一个创建一系列相关或互相依赖对象的接口,而无需明确指定它们的具体的类

2. 别名

Kit

3. 动机

考虑一组支持多种视感(look-and-feel)标准的用户界面工具包,例如Motif和Presentation Manager。不同的视感风格为诸如滚动条、窗口和按钮等用户界面”窗口组件“定义不同的外观和行为。为保证视感风格标准间的可移植性,一个应用不应该为一个特定的视感外观硬编码它的窗口组件。在整个应用中实例化特定既视感风格窗口组件类将使得以后很难改变视感风格。

为解决这一问题我们可以定义一个抽象的WidgeFactory类,这个类声明了一个用来创建每一个类基本窗口组件的接口。每一类窗口组件都有一个抽象类,而具体子类则实现了窗口组件的特定视感风格。对于每一个抽象窗口组件类,WidgetFactory接口都有一个返回新窗口组件对象的操作。客户调用这些操作以获得窗口组件实例,但客户并不知道他们他们正在使用的是哪些具体类。这样客户就不依赖于一般的视感风格,如下页图所示:

抽象工厂(Abstract Factory)模式_第1张图片

每一种视感标准都对应一个具体的WidgetFactory子类。每一子类实现那些勇于创建合适视感风格窗口组件的操作。例如,MotifWidgetFactory的CreateScrollBar操作实例化并返回一个Motif滚动条,而相应的PMWidgetFactory操作返回一个Presentation Manager的滚动条。客户仅通过WidgetFactory接口创建组件,它们并不知道哪些类实现了既定视感风格的窗口组件。换言之,客户仅与抽象类定义的接口交互,而不使用特定的具体类的接口。

WidgetFactory也增强了具体窗口组件类之间依赖关系。一个Motif的滚动条应该与Motif按钮、Motif正文编辑器一起使用,这一约束条件作为使用MotifWidgetFactory的结果被自动加上。

4. 适用性

在以下情况可以使用Abstract Factory模式

  • 一个系统要独立于它的产品的创建、组合和表示时
  • 一个系统要由多个产品系列中的一个来配置时
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时

5. 结构

此模式的结果如下图所示:
抽象工厂(Abstract Factory)模式_第2张图片

6. 参与者

  • AbstractFactory(WidgetFactory)

    ——声明一个创建抽象产品对象的操作接口

  • ConcreteFactory(MotifyWidgetFactorym PMWidgetFactory)

    ——实现创建具体产品的操作

  • AbstractProduct(Window,ScrollBar)

    ——为一类产品对象声明一个接口(并不是指实际接口,而指抽象方法,一个入口)

  • ConcreteProduct(MotifyWindow,MotifScrollBar)

    ——定义一个将被相应的具体工厂创建的产品对象

    ——实现AbstractProduct接口

  • Client

    ——仅使用有AbstractFactory和AbstractProduct类声明的接口。

7. 协作

  • 通常在运行时刻创建ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应该使用不同的具体工厂。
  • AbstractFactory将产品对象的创建延迟到它的子类ConcreteFactory子类

8. 效果

Abstract Factory模式有下面的一些优点和缺点:

优点:

  • 1.它分离了具体的类 Abstract Factory模式帮助控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。
  • 2.它使得易于交换产品系列 一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候,这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。在我们用户界面的例子中,我们仅需转换到相应的工厂对象并重新创建接口,就可实现从Motif窗口组件转换为Presentation Manager窗口组件(例如ConcreteFactory1可以改变为返回ProductB2)。
  • 3.它有利于产品的一致性 当一个系列中的产品对象呗设计成一起工作是,一个应用一次只能使用同一个系列中的对象,这一点很重要,而AbstractFactory很容易实现这一点。

缺点:

  • 难以支持新种类的产品 难以扩展抽象工厂以生产新种类的产品。这是因为Abstract Factory接口确定了可以被创建的产品的集合。支持新种类的产品就需要扩展该工厂接口,这将涉及Abstract Factory类及所有子类的改变。我们会在实现一节讨论这个问题的一个解决办法

9. 实现

下面是实现Abstract Factory模式的一些应该注意的点:

  • 1.将工厂作为单件 一个应用中一般每个产品系列只需要一个ConcreteFactory的实例,因此工厂通常最好实现在一个Singleton。

  • 2.创建产品 Abstract Factory仅声明一个创建产品的接口,真正创建产品是由ConcreteProduct子类实现的。最通常的一个办法是为每一个产品定义一个工厂方法。一个具体的工厂将为每个产品重定义该工厂方法以指定产品。虽然这样的实现很简单,但他却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。

    如果有多个可能的产品系列。具体工厂也可以使用Prototype来实现。具体工厂使用产品系列中的每一个产品的原型实例来初始化,且它通过复杂它的原型来创建新的产品。在基于原型的方法中,使得不是每个新的产品系列都需要一个新的具体工厂类。具体的Java例子 ?

  • 3.定义可扩展的工厂 Abstract Factory通常为每一个它可以生产的产品定义一个操作。产品的种类被编码在操作型构中。增加一种新的产品要求编码AbtractFactory的接口以及所有与它相关的类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数。该参数指指定了将被创建的对象的种类。它可以是一个类标识符、一个整数、一个字符串,或其他任何可以标识这种产品的东西。实际上使用这种方法,Abstract Factory只需要一个”Make“操作和一个指示要创建对象的种类的参数。这是前面已经讨论过的基于原型的和基于类的抽象工厂的抽象工厂的技术。

    与C++这样的静态类型语言相比,这一变化容易用在类似于Smalltalk这样的动态类型言语中。仅当所有的对象都有相同的抽象基类,或者当产品对象可以被请求它们的客户安全的强制转换成正确的类型时,你才能在C++中使用它,Factory的实现部分说明了怎么样在C++实现这样的参数化操作。

    该方法即使不需要类型强制转化,但仍有一个本质的问题:所有的产品将返回类型所给定的相同的抽象接口返回给客户。客户将不能区分或者对一个产品的类别进行安全的假定。如果一个客户需要进行与特定子类相关的操作,而这个操作却不能通过抽象接口得到。虽然客户可以实施一个向下类型转化(例如在C++中用dynamic_cast),但这并不总是可行或安全的,因为向下类型转化可能会失败。这个是一个典型的高度灵活和可扩展接口的权衡折衷。

10. 代码示例

首先是创建AbstractFactory——PizzaIngredientFactory.java

public interface PizzaIngredientFactory {
	
	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClam();
}

上述提到对具体工厂使用单例,有利于节省成本,当下比较好用的单例应该是其他开发者在stackoverflow向我推荐的使用Enum作为单例,并且在某些需要同步的方法上使用Synchronized关键字

ConcreteFactory——ChicagoPizzaIngredientFactory.java & ChicagoPizzaIngredientFactory.java

ChicagoPizzaIngredientFactory.java

public enum ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
	CHICAGO_FACTORY_INSTANCE;
	
	@Override
	public synchronized Dough createDough() {
		return new ThickCrustDough();
	}

	@Override
	public synchronized Sauce createSauce() {
		return new PlumTomatoSauce();
	}

	@Override
	public synchronized Cheese createCheese() {
		return new MozzarellaCheese();
	}

	@Override
	public synchronized Veggies[] createVeggies() {
		Veggies veggies[] = { new BlackOlives(), 
		                      new Spinach(), 
		                      new Eggplant() };
		return veggies;
	}

	@Override
	public synchronized Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}

	@Override
	public synchronized Clams createClam() {
		return new FrozenClams();
	}
}

ChicagoPizzaIngredientFactory.java

public enum NYPizzaIngredientFactory implements PizzaIngredientFactory {
	NY_FACTORY_INSTANCE;
	
	@Override
	public synchronized Dough createDough() {
		return new ThinCrustDough();
	}
	
	@Override
	public synchronized Sauce createSauce() {
		return new MarinaraSauce();
	}
	
	@Override
	public synchronized Cheese createCheese() {
		return new ReggianoCheese();
	}
	
	@Override
	public synchronized Veggies[] createVeggies() {
		Veggies[] veggies = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
		return veggies;
	}
	
	@Override
	public synchronized Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}
	
	@Override
	public synchronized Clams createClam() {
		return new FreshClams();
	}
}

有AbstractProduct太多我这里只列举两个:

Dough.java

public interface Dough {	
	String toString();
}

Sauce.java

public interface Sauce {
	String toString();
}

接着是ConcreteProduct:

ThickCrustDough.java

public class ThickCrustDough implements Dough {
	
	@Override
	public String toString() {
		return "ThickCrust style extra thick crust dough";
	}
}

ThinCrustDough.java

public class ThinCrustDough implements Dough{
	
	@Override
	public String toString() {
		return "Thin Crust Dough";
	}
}

PlumTomatoSauce.java

public class PlumTomatoSauce implements Sauce{
	
	@Override
	public String toString() {
		return "Tomato sauce with plum tomatoes";
	}
}

MarinaraSauce.java

public class MarinaraSauce implements Sauce{
	
	@Override
	public String toString() {
		return "Marinara Sauce";
	}
}


其他的原料类因为篇幅过大省略了。

接下来是调用的辅助类——PizzaStore.java

public abstract class PizzaStore {
 
	protected abstract Pizza createPizza(String item);
 
	public final Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}


及其两个具体辅助类:

ChicagoPizzaStore.java

public class ChicagoPizzaStore extends PizzaStore {

	protected Pizza createPizza(String item) {
		Pizza pizza = null;
		PizzaIngredientFactory ingredientFactory =
		 ChicagoPizzaIngredientFactory.CHICAGO_FACTORY_INSTANCE;

		if (item.equals("cheese")) {
			pizza = new CheesePizza(ingredientFactory);
			pizza.setName("Chicago Style Cheese Pizza");
		} else if (item.equals("veggie")) {
			pizza = new VeggiePizza(ingredientFactory);
			pizza.setName("Chicago Style Veggie Pizza");
		} else if (item.equals("clam")) {
			pizza = new ClamPizza(ingredientFactory);
			pizza.setName("Chicago Style Clam Pizza");
		} else if (item.equals("pepperoni")) {
			pizza = new PepperoniPizza(ingredientFactory);
			pizza.setName("Chicago Style Pepperoni Pizza");
		}
		return pizza;
	}
}

NYPizzaStore.java

public class NYPizzaStore extends PizzaStore{
	protected Pizza createPizza(String item) {
		Pizza pizza = null;
		PizzaIngredientFactory ingredientFactory = 
				NYPizzaIngredientFactory.NY_FACTORY_INSTANCE;
		
		if (item.equals("cheese")) {
			pizza = new CheesePizza(ingredientFactory);
			pizza.setName("New York Style Cheese Pizza");
		} else if (item.equals("veggie")) {
			pizza = new VeggiePizza(ingredientFactory);
			pizza.setName("New York Style Veggie Pizza");
		} else if (item.equals("clam")) {
			pizza = new ClamPizza(ingredientFactory);
			pizza.setName("New York Style Clam Pizza");
		} else if (item.equals("pepperoni")) {
			pizza = new PepperoniPizza(ingredientFactory);
			pizza.setName("New York Style Pepperoni Pizza");
		}
		return pizza;
	}
}

然后是测试类Client——PizzaTestDrive.java

public class PizzaTestDrive {
	 
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoStore = new ChicagoPizzaStore();
 
		Pizza pizza = nyStore.orderPizza("cheese");
		System.out.println("Ethan ordered a " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza("cheese");
		System.out.println("Joel ordered a " + pizza + "\n");
	}
}

以及对应的测试结果

--- Making a New York Style Cheese Pizza ---
Preparing New York Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Cheese Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
 

--- Making a Chicago Style Cheese Pizza ---
Preparing Chicago Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Cheese Pizza ----
ThickCrust style extra thick crust dough
Tomato sauce with plum tomatoes
Shredded Mozzarella


最后附上Pizza类的UML图:
抽象工厂(Abstract Factory)模式_第3张图片

11. 已知应用

InterView使用"Kit"后缀来表示AbstractFactory类。它定义WidgetKit和DialogKit抽象工厂来生成与特定视感相关的用户界面对象。InterView还包括一个LayoutKit,它根据所需要的布局生成不同的组合(composition)对象。例如,一个概念上是水平的布局根据文档的定位(画像或是风景)可能需要不同的组成对象。

ET++使用Abstract Factory模式以达到在不同窗口系统(例如,X Windows和SunViem)间可移植性。Window System抽象基类定义一些接口,来创建表示窗口系统资源的对象。具体子类为某个特定的窗口系统实现这些接口。运行时刻,ET++创建一个具体WindowSystem子类的实例,以创建具体的系统资源对象。

12. 相关模式

  • Factory Method & ProxyType :Abstract Factory类通常用工厂方法实现(比如用子类工厂去返回FrozenClams)这就是工厂方法实现,但他们也可以用Prototype实现。
  • Singleton :一个具体的工厂通常是一个单件。

13. 设计原则口袋

  • 封装变化
  • 针对接口编程,而不是针对实现编程
  • 类应该对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类
  • 多用组合,少用继承
  • 为交互对象之间的松耦合设计而努力

14. 参考文献

《HeadFirst设计模式》

《设计模式:可复用面对对象软件的基础》

你可能感兴趣的:(设计模式)