工厂模式

文章说明

  • 该文章为《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 orderPizza() SimplePizzaFactory createPizza() «abstract» Pizza prepare() bake() cut() box() CheesePizza VeggiePizza ClamPizza PepperoniPizza has-a has-a

部分代码的展示

------------------客户类------------------
披萨店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,并且根据当地区域创建适合本地风味的披萨店
  • 披萨店与地区进行了绑定,顾客去哪个区域的披萨店就决定了得到哪种风味的披萨店,去纽约的披萨店,那就是纽约风味的披萨。

类图

«abstract» PizzaStore orderPizza() createPizza() NYPizzaStore createPizza() ChicagoPizzaStore createPizza() NYPizzaFactory createPizza() ChicagoPizzaFactory createPizza() has-a has-a

需要更多的质量控制

  • 加盟店采用指定工厂创建披萨,但是其他流程希望给加盟店一些扩展性比如纽约披萨店的切片切成方形,烘焙温度和时间不同等
  • 希望建立一个框架,把加盟店和创建披萨捆绑在一起的同时又保持一点的弹性

类图

工厂方法模式

«abstract» PizzaStore orderPizza() createPizza() NYPizzaStore createPizza() ChicagoPizzaStore createPizza() «abstract» Pizza name dough sauce toppings prepare() bake() cut() box() getName() NYStyleCheesePizza NYStyleVeggiePizza ChicagoStyleCheesePizza ChicagoStyleVeggiePizza
«abstract» Pizza «abstract» PizzaStore has-a

平行的类层级

  • 两个类层级产品类、创建者类)都有抽象类,而抽象类都有具体的子类,每个子类都有自己特定的实现

类图

«abstract» PizzaStore orderPizza() createPizza() NYPizzaStore createPizza() ChicagoPizzaStore createPizza() «abstract» Pizza NYStyleCheesePizza NYStyleVeggiePizza ChicagoStyleCheesePizza ChicagoStyleVeggiePizza is-a is-a 封装知识 封装知识 封装知识 封装知识

要点

框架
  • 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

定义抽象工厂模式

确保原料一致

  • 让不同的区域的加盟店在原料工厂里获取一组原料,当然原料工厂在不同的区域也不同,比如纽约原料工厂的番茄酱料是加大蒜的,芝加哥的则是普通的番茄酱

定义抽象工厂

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。

类图

«Interface» AbstractFactory createProduceA() createProduceB() ConcreteFactory1 createProduceA() createProduceB() ConcreteFactory2 createProduceA() createProduceB() «Interface» AbstractProduceA «Interface» AbstractProduceB Client ProduceA1 ProduceA2 ProduceB1 ProduceB2

部分代码的展示

------------------工厂类------------------
"抽象"原料工厂类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 NYStyleCheesePizza NYStyleClamPizza ChicagoStyleCheesePizza ChicagoStyleClamPizza

原则的应用

«abstract» Pizza PizzaStore NYStyleCheesePizza NYStyleClamPizza ChicagoStyleCheesePizza ChicagoStyleClamPizza
  • 高层组件(PizzaStore)低层组件(这些披萨)依赖Pizza抽象类

要点

遵循原则
  • 工厂方法并非唯一的技巧,但是确实最有威力的。
倒置在哪?
  • 与一般OO设计思考方式完全相反。与仅有高层组件依赖低层组件不同,现在低层组件竟然也依赖高层组件,倒置了依赖。

指导方针

  • 变量不可以持有具体类的引用
    使用工厂
  • 不要让类派生自具体类
    派生自抽象
  • 不要覆盖基类已实现的方法
    已实现的方法应该由子类共享

工厂方法和抽象工厂

所有的工厂都是用来封装对象的创建的
工厂方法:通过 继承
抽象工厂:通过 对象的组合
让客户从具体类型中解耦
工厂方法: 客户只需要知道他们使用的抽象类型就可以了,由子类负责从决定具体类型
抽象工厂: 提供一个用来创建一个产品家族的抽象类型,该类型的子类定义了产品被生产的方式
扩展这组相关产品
抽象工厂:必须改变 接口,这是个严重的问题
组合使用
抽象工厂: 具体工厂经常 实现工厂方法来创建他们的产品
何时使用
抽象工厂: 需要创建产品家族和想让制造的相关产品集合起来的时候
工厂方法: 客户代码需要从实例化的具体类中解耦,或者 目前不知道将来需要实例化哪些具体类的时候
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值