工厂模式(factory)
文章说明
- 该文章为
《Head First 设计模式》
的学习笔记 - 非常推荐大家去买一本
《Head First 设计模式》
去看看,节奏轻松愉悦,讲得通俗易懂,非常适合想要学习、了解、应用设计模式以及OO设计原则的小白。
定义简单工厂
- 并非是设计模式,更像是一种编程习惯
- 简单来说,就是将创建具体对象的代码封装到一个对象里,我们称这个对象为"工厂"
原来的设计
public Pizza orderPizza(String type) {
Pizza pizza = null;
// 这里是变化的部分:
if(type.equals("cheese")) // 奶酪
pizza = new CheesePizza();
else if (type.equals("pepperoni")) // 意大利辣香肠
pizza = new PepperoniPizza();
else if (type.equals("clam")) // 蛤蜊
pizza = new ClamPizza();
else if (type.equals("veggie")) // 蔬菜
pizza = new VeggiePizza();
// 这里是不需要改变的部分:
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
缺陷
- 耦合度高。上例代码,没有封装变化,也没有对修改关闭,即当我需要更多的披萨类型时,我们需要改变
PizzaStore
源代码,这意味着该设计可维护性极差
接下来,我们套用简单工厂的技巧,对原来糟糕的设计进行改造:
类图
部分代码的展示
------------------客户类------------------
披萨店: PizzaStore.java
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
// 通过"工厂"选择披萨类型
Pizza pizza = factory.createPizza(type);
// 披萨准备,烘焙,切片,装盒的顺序操作
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
------------------工厂类------------------
披萨工厂:SimplePizzaFactory.java
/**
* 定义简单工厂
* 编程习惯
*/
public class SimplePizzaFactory {
// 该方法经常被定义为static,该技巧称为"静态工厂"
public Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) // 奶酪
pizza = new CheesePizza();
else if (type.equals("pepperoni")) // 意大利辣香肠
pizza = new PepperoniPizza();
else if (type.equals("clam")) // 蛤蜊
pizza = new ClamPizza();
else if (type.equals("veggie")) // 蔬菜
pizza = new VeggiePizza();
return pizza;
}
}
要点
-
优点
-
- 现在我们添加新的披萨类型就不需要修改
PizzaStore
类了,因为我们遵循了设计原则封装变化,将披萨(pizza)的使用和实例化解耦合,但是还远远不够。
客户
- 现在我们添加新的披萨类型就不需要修改
-
SimplePizzaFactory
可有许多的客户而orderPizza()
方法是它的客户之一,通过组合建立客户关系,当然,我们也要把具体实例化的代码从客户代码中删除。
静态工厂
-
- 利用静态方法定义一个简单的工厂,也是常见的技巧,被称为静态工厂,这样做就不用创建工厂对象了,当然也有缺点,就是不能通过继承改变创建方法的行为。
定义工厂方法模式
加盟店
- 现在我们要根据不同的地区建立不同的加盟店,那
PizzaStore
类应该被定义为抽象类,让子类扩展自PizzaStore
,并且根据当地区域创建适合本地风味的披萨店 - 披萨店与地区进行了绑定,顾客去哪个区域的披萨店就决定了得到哪种风味的披萨店,即去纽约的披萨店,那就是纽约风味的披萨。
类图
需要更多的质量控制
- 加盟店采用指定工厂创建披萨,但是其他流程希望给加盟店一些扩展性,比如纽约披萨店的切片切成方形,烘焙温度和时间不同等
- 希望建立一个框架,把加盟店和创建披萨捆绑在一起的同时又保持一点的弹性。
类图
工厂方法模式:
平行的类层级
- 两个类层级(
产品类、创建者类
)都有抽象类,而抽象类都有具体的子类,每个子类都有自己特定的实现。
类图
要点
-
框架
-
orderPizza()
与工厂联合起来就可以成为一个框架。
-
- 工厂方法
createPizza()
将生产知识封装进各个创建者,这样的做法,也可以被视为框架。
- 工厂方法
-
- 工厂方法就是封装这类知识的关键
部分代码的展示
------------------创建者Creator
类------------------
抽象披萨店类:PizzaStore.java
public abstract class PizzaStore {
// orderPizza 可以声明为final,以防止被子类覆盖
public Pizza orderPizza(String type) {
// 使用工厂方法,让一群子类负责实例化
Pizza pizza = createPizza(type);
// 一系列的工序准备工作,烘焙,切片,装盒
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/**
* 工厂方法技术
* 实例化披萨的责任被移到一个"方法"中,此方法就如同一个"工厂"
*/
protected abstract Pizza createPizza(String type);
}
具体披萨店类:NYPizzaStore.java
芝加哥风味的披萨店也是同理的,这里不展示了
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) // 奶酪
pizza = new NYStyleCheesePizza();
else if (type.equals("pepperoni")) // 意大利辣香肠
pizza = new NYStylePepperoniPizza();
else if (type.equals("clam")) // 蛤蜊
pizza = new NYStyleClamPizza();
else if (type.equals("veggie")) // 蔬菜
pizza = new NYStyleVeggiePizza();
return pizza;
}
}
------------------产品Produce
类------------------
抽象的披萨类:Pizza.java
public abstract class Pizza {
String name;
String dough; // 面团
String sauce; // 酱料
ArrayList<String> toppings // 一堆佐料
= new ArrayList<String>();
void prepare() { // 准备
System.out.println("Prepare " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for(String topping : toppings)
System.out.println(topping);
}
void bake() { // 烘焙
System.out.println("Bake for 25 minutes at 350");
}
void cut() { // 切片
System.out.println("Cutting the pizza into diagonal slices");
}
void box() { // 装盒
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
public String toString() { // 添加打印披萨的代码... }
}
具体的披萨类:ChicagoStyleCheesePizza.java
纽约风味的披萨也是同理的,这里不展示了
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "Chicago Style Sauce and Cheese Pizza";
dough = "Extra Thin Crust Dough"; // 薄地壳比萨生面团
sauce = "Plum Tomato Sauce"; // 小番茄酱
toppings.add("Shredded Mozzarella Cheese"); // 意大利白干酪
}
// 将披萨切成正方形
@Override void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
定义测试类:PizzaTestDrive.java
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore
nyStore = new NYPizzaStore(), // 纽约披萨店
chicagoStore = new ChicagoPizzaStore(); // 芝加哥披萨店
// 纽约风味的奶酪披萨
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Orderd a " + pizza.getName() + "\n");
// 芝加哥风味的奶酪披萨
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Orderd a " + pizza.getName() + "\n");
}
}
跑起来:
Prepare NY Style Sauce and Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings:
Grated Reggiano Cheese
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Orderd a NY Style Sauce and Cheese Pizza
Prepare Chicago Style Sauce and Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings:
Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Orderd a Chicago Style Sauce and Cheese Pizza
定义工厂方法
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
- 工厂方法让子类决定要实例化哪一个,要搞清楚,所谓的"决定",是指编写创建者的时候,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品。
本例中PizzaStore
定义的一个抽象方法cretaePizza
就是工厂方法技巧:
abstract Pizza createPizza(String type);
要点
-
耦合度低
-
- 如果增加产品或者改变产品的实现,
创建者Creator类
并不会受到影响,因为Creator类与产品Produce类
不是紧耦合的。
工厂方法和创建者可以不是抽象的
- 如果增加产品或者改变产品的实现,
-
- 可以定义一个默认的工厂方法,及时创建者没有任何子类,依然可以创建产品;本例子中具体的创建者类有公共的接口,所以创建者为抽象类更有利于代码的复用,如果子类没有公共接口,创建者可以是一个
接口Interface
。
参数化工厂方法
- 可以定义一个默认的工厂方法,及时创建者没有任何子类,依然可以创建产品;本例子中具体的创建者类有公共的接口,所以创建者为抽象类更有利于代码的复用,如果子类没有公共接口,创建者可以是一个
-
- 本例子的工厂方法是带参数的,目的是根据参数创建不同的对象,若工厂只产生一种对象,则不需要参数化。
字符串传入参数化类型的问题
-
- 这样的实现很危险,因为很容易造成所谓的"运行时错误"。这样的错误难以发现,想像一下,你将"
cheese
“误写成”chesse
",可怕的是,这样的错误,编译器发现不了。
有几个复杂的技巧可以解决这个问题。
- 这样的实现很危险,因为很容易造成所谓的"运行时错误"。这样的错误难以发现,想像一下,你将"
- 创建代表参数类型的对象
- 使用 静态变量
-
使用Java5支持的
enum
定义抽象工厂模式
确保原料一致
- 让不同的区域的加盟店在原料工厂里获取一组原料,当然原料工厂在不同的区域也不同,比如纽约原料工厂的番茄酱料是加大蒜的,芝加哥的则是普通的番茄酱
定义抽象工厂
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。
类图
部分代码的展示
------------------工厂类------------------
"抽象"原料工厂类:PizzaIngredientFactory.java
/**
* 原料工厂类
* 使用工厂方法定义了一组原料接口
*/
public interface PizzaIngredientFactory {
Dough createDough(); // 面团
Sauce createSauce(); // 酱料
Cheese createCheese(); // 奶酪
Veggies[] createVeggies(); // 蔬菜
Pepperoni createPepperoni(); // 意大利辣味香肠
Clams createClams(); // 蛤蜊
}
具体的原料工厂:NYPizzaIngredientFactory.java
芝加哥原料工厂是同理的,这里不展示了
// 纽约原料工厂类
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
public Veggies[] createVeggies() {
Veggies[] veggies = { // 暂时写死蔬菜方便实现
new Garlic(), new Onion(),
new Mushroom(), new RedPepper() };
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClams() {
return new FreshClams();
}
}
重写抽象披萨类:Pizza.java
public abstract class Pizza {
String name;
Dough dough; // 面团
Sauce sauce; // 酱料
Veggies[] veggies; // 蔬菜
Cheese cheese; // 奶酪
Pepperoni pepperoni; // 意大利辣味香肠
Clams clam; // 蛤蜊
// 工厂方法
abstract void prepare();
void bake() { // 烘焙
System.out.println("Bake for 25 minutes at 350");
}
void cut() { // 切片
System.out.println("Cutting the pizza into diagonal slices");
}
void box() { // 装盒
System.out.println("Place pizza in official PizzaStore box");
}
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public String toString() { // 打印披萨 }
}
------------------产品类------------------
具体的披萨们:CheesePizza.java
其他的披萨根据不同的情况,有些许不同,这里不展示了
// 使用原料工厂做的奶酪披萨
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory f) {
ingredientFactory = f;
}
@Override void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
// 使用原料工厂做的蛤蜊奶酪披萨
public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory f) {
ingredientFactory = f;
}
@Override void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clam = ingredientFactory.createClams();
}
}
------------------客户类------------------
具体的披萨店类:NYPizzaStore.java
芝加哥披萨店不展示了
// 纽约披萨店
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory
= new NYPizzaIngredientFactory();
if(type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Styles Cheese Pizza");
} else if (type.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (type.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
依赖倒置原则
定义
要依赖抽象,不要依赖具体类
类图
过度的对象依赖类图:
原则的应用:
高层组件(PizzaStore)
和低层组件(这些披萨)
都依赖了Pizza抽象类
。
要点
-
遵循原则
-
- 工厂方法并非唯一的技巧,但是确实最有威力的。
倒置在哪?
-
- 与一般OO设计思考方式完全相反。与仅有高层组件依赖低层组件不同,现在低层组件竟然也依赖高层组件,倒置了依赖。
指导方针
- 变量不可以持有具体类的引用
使用工厂 - 不要让类派生自具体类
派生自抽象 - 不要覆盖基类已实现的方法
已实现的方法应该由子类共享
工厂方法和抽象工厂
-
所有的工厂都是用来封装对象的创建的
- 工厂方法:通过 继承
- 抽象工厂:通过 对象的组合 让客户从具体类型中解耦
- 工厂方法: 客户只需要知道他们使用的抽象类型就可以了,由子类负责从决定具体类型
- 抽象工厂: 提供一个用来创建一个产品家族的抽象类型,该类型的子类定义了产品被生产的方式 扩展这组相关产品
- 抽象工厂:必须改变 接口,这是个严重的问题 组合使用
- 抽象工厂: 具体工厂经常 实现工厂方法来创建他们的产品 何时使用
- 抽象工厂: 需要创建产品家族和想让制造的相关产品集合起来的时候
- 工厂方法: 客户代码需要从实例化的具体类中解耦,或者 目前不知道将来需要实例化哪些具体类的时候