工厂模式
当使用 new 时,我们是在实例化一个具体类,用的是实现,而不是接口。代码绑定着具体类会导致代码更脆弱,更缺乏弹性。
封装创建对象的代码,放到一个“工厂”里。工厂处理创建对象的细节。
松耦合的OO设计。
简单工厂模式
定义
简单工厂其实不是一个设计模式,反而比较像一种变成习惯。
例子
- 工厂类
/**
* 简单工厂类,负责创建对象
*/
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
switch (type){
case "cheese":
pizza = new CheesePizza();
break;
case "pepperoni":
pizza = new Pepperonipizza();
break;
case "clam":
pizza = new ClamPizza();
break;
case "veggie":
pizza = new VeggiePizza();
break;
}
return pizza;
}
}
- 使用类
/**
* 披萨店
* 调用工厂生产披萨
*/
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
/**
* 传入订单类型来使用工厂创建披萨
* @param type
* @return
*/
public Pizza orderPizza(String type){
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//其他方法
}
类图
角色:
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。
分析
下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式,每增加一种新的披萨,需要在工厂类中增加相应的创建业务逻辑(switch 中添加 case),违背开闭原则。
静态工厂
利用静态方法定义一个简单的工厂,这是很常见的,称为静态工厂。为何这样用?因为不需要使用创建对象的方法来实例化对象。但是也有缺点,不能通过继承来改变创建方法的行为。
工厂方法模式
定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式让一个类的实例化延迟到其子类。
工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。
结构
例子
- 创建者(Creator)类
/**
* 抽象的披萨店
*/
public abstract class PizzaStore {
/**
* 传入订单类型来使用工厂方法创建披萨
* @param type
* @return
*/
public Pizza orderPizza(String type){
Pizza pizza;
//创建
pizza = createPizza(type);
// pizza.prepare();
// pizza.bake();
// pizza.cut();
// pizza.box();
return pizza;
}
//实例化披萨的责任被移到一个“方法”中,此方法就如同是一个“工厂”
abstract Pizza createPizza(String type);
//其他方法
}
- 具体创建者
/**
* 加盟店 纽约店创建当地风味披萨
* 返回Pizza由子类全权负责实例化哪一个具体Pizza
*/
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
switch (type){
case "cheese":
pizza = new NYStyleCheesePizza();
break;
case "pepperoni":
pizza = new NYStylePepperoniPizza();
break;
case "clam":
pizza = new NYStyleClamPizza();
break;
case "veggie":
pizza = new NYStyleVeggiePizza();
break;
}
return pizza;
}
}
- 产品类
/**
* 产品类
*/
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
void prepare(){
System.out.println("Preparing" + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings:");
for (int i = 0; i < toppings.size(); i++){
System.out.println(" " + toppings.get(i));
}
}
void bake(){
System.out.println("Baking...");
}
void cut(){
System.out.println("Cutting...");
}
void box(){
System.out.println("Boxing...");
}
public String getName() {
return name;
}
}
- 具体产品
/**
* 抽象类Pizza的具体子类
*/
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
- 使用
PizzaStore nyStore = new NYPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("your ordered a " + pizza.getName() + "\n");
类图
- 创建者类
- 产品类
分析
参与角色
- Product(document)定义工厂方法所创建对象的接口。
- ConcreteProduct(mydocument)实现 product 接口。
- Creator(application)声明工厂方法,可以调用工厂方法以创建一个product对象
- ConcreteCreator (MyApplication)重新定义工厂方法,以返回一个ConcreteProduct实例
总结
工厂方法模式将产品的“实现”从“使用”中解耦,如果增加产品或改变产品的实现,Creator 不会受到影响,因为 Creator 与任何ConcreteProduct 之间都不是紧耦合。
简单工厂和工厂方法的区别:
工厂方法的具体创建者类看起来像是简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。
抽象工厂模式
定义
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
例子
披萨店要统一原料,由一个原料工厂来提供各种原料:
工厂接口
public interface PizzaIngredientFactory{ public Dough createDough(); public Sauce createSauce(); public Cheese creatrCheese(); ... }
为每一个区域建造一个工厂,如:纽约原料工厂:
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{ public Dough createDough(){ return new THinCrustDough(); } public Sauce createSauce(){ return new MarinaraSauce(); } ... }
重做披萨,使用工厂生产出来的原料
/**
* 产品类
*/
public abstract class Pizza {
String name;
//一组原料
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
...
//抽象方法,收集制作披萨的原料,来自原料工厂
abstract void prepare();
void bake(){
System.out.println("Baking...");
}
void cut(){
System.out.println("Cutting...");
}
void box(){
System.out.println("Boxing...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 具体产品类 调用原料工厂
public class CheesePizza extends Pizza {
PizzaIngredientFactory indgredientFactory;
//构造函数传入原料工厂
public CheesePizza(PizzaIngredientFactory indgredientFactory;){
this.indgredientFactory = indgredientFactory;
}
void prepare(){
System.out.println("preparing" + name);
indgredientFactory.createDough();
indgredientFactory.createSauce();
indgredientFactory.createCheese();
}
}
- 具体创建者类 再调用原料工厂
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
NYPizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
switch (type){
case "cheese":
pizza = new NYStyleCheesePizza(ingredientFactory);
pizza.setName("cheese");
break;
case "pepperoni":
pizza = new NYStylePepperoniPizza(ingredientFactory);
pizza.setName("pepperoni");
break;
case "clam":
pizza = new NYStyleClamPizza(ingredientFactory);
pizza.setName("clam");
break;
case "veggie":
pizza = new NYStyleVeggiePizza(ingredientFactory);
pizza.setName("veggie");
break;
}
return pizza;
}
}
- 过程
//创建一个具体生产者的实例 一个纽约披萨店
PizzaStore nyPizzaStore = new NYPizzaStore();
//接受订单
nyPizzaStore.orderPizza("cheese");
//orderPizza 调用 createPizza方法
Pizza pizza = createPizza("cheese");
//涉及原料工厂
Pizza pizza = new CheesePizza(nyIngredientFactory);
//接下来要准备披萨,一旦调用 prepare方法,工厂将被要求准备原料
void prepare(){
dough = factory.createDough();
sauce = factory.createSauce();
...
}
总结
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。
抽象工厂与工厂方法的区别
工厂方法:创建对象用的方法时继承。使用工厂方法创建一个对象,需要扩张一个类,并覆盖它的工厂方法。
抽象工厂:创建对象通过对象的组合。提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义列产品被产生的方法。
设计原则
依赖倒置原则: 要依赖抽象,不要依赖具体类。
跟针对接口编程,不针对实现编程有点类似,然而这里更强调“抽象”。这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层组件或底层组件,两者都应该依赖于抽象。
所谓高层组件,是由其他底层组件定义其行为的类,例如 PizzaStore 是高层组件,因为它的行为是由披萨定义的: PizzaStore 创建不同的披萨对象。而披萨本身属于底层组件。
例如工厂方法中,底层组件具体具体产品类(具体披萨类)依赖于高层的抽象(Pizza类),同样的高层组件也依赖于相同的抽象(抽象PizazStore依赖Pizza抽象)。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
- 变量不可以持有具体类的引用(用 new 就会持有具体类的引用,可以用工厂模式避开)。
- 不要让类派生自具体类(如果派生自具体类,就会依赖于具体类。请派生自一个抽象(接口或抽象类))。
- 不要覆盖基类中的方法(如果覆盖,那么基类就不是一个适合被继承的抽象,基类中实现的方法,应该由子类共享)。