Head First 设计模式笔记 4.工厂模式

摘要

这篇博客记录了new操作符新建对象不够灵活的问题,通过一个披萨系统讲解了简单工厂,工厂方法模式,抽象工厂模式。并对依赖倒置原则进行了简单的说明。

new的噩梦

小明又接到了新活,这次甲方是一家披萨店,要求设计一个披萨订单系统。作为一名优秀的程序设计师,小明很快有了思路。

//返回一个做好的披萨
Pizza orderPizza(){
     
	Pizza pizza = new Pizza()
	//下面是披萨的加工,烘烤,切片,装箱的过程
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

但是过了几天,披萨店老板表示:"小明,时代变了。现在我们有很多种披萨,我们要求用户要什么披萨,我们就做什么披萨。”

小明想,这个简单,只要把披萨抽象成一个接口,再用不同种类的披萨去继承它就好了。最后根据用户输入的字符串,生成所需的披萨。

//根据类型返回一个用户选定的披萨
Pizza orderPizza(String type){
     
	Pizza pizza;
	
	//根据用户的口味生成不同的披萨
	if(type.equals("cheese"))
		pizza = new CheesePizza();
	else if(type.equals("greek"))
		pizza = new GreekPizza();
	else if(type.equals("pipperoni"))
		pizza = new Pipperoni();
	
	//下面是披萨的加工,烘烤,切片,装箱的过程
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

然而过了几天,老板表示:“小明,时代又变了,有的披萨经营不善,我们不做了。我们又加入了一些新的披萨种类。”这意味着小明又得忙活了。

然而就算这次修改了代码,随着时间过去,菜单改变,代码必须一改再改。这里的代码没有对修改封闭。

Head First 设计模式笔记 4.工厂模式_第1张图片
图1 不断修改的代码

问题出在哪里呢?小明仔细观察了一下代码,哦,原来问题出在了new这个关键字上。当看到new,就会想到具体。例如

Pizza pizza = new CheesePizza();

这里等号左边Pizza pizza使用接口的确让代码更具备弹性,但是等号右边new CheesePizza()还是得建立具体的实例。当使用具体类的时候,一旦引入新的具体类或者修改具体类,就必须修改代码。能不能不用new就新建对象呢?小明心想。

简单工厂

小明补习了设计模式秘籍,终于找到了好办法。秘籍告诉他,有一种方法叫简单工厂,把创建披萨的代码移到另一个对象,由这个对象专门负责制造披萨。这个负责制造披萨的对象,我们就叫它披萨工厂吧。

披萨工厂SimplePizzaFactory的代码。

public class SimplePizzaFactory {
     

	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;
	}
}

没有neworderPizza方法。再也无需再关心它所要制造什么种类的披萨,如果要修改披萨种类,只需要修改工厂代码就可以了。

public class PizzaStore {
     
	SimplePizzaFactory factory;
 
	public PizzaStore(SimplePizzaFactory factory) {
      
		this.factory = factory;
	}
 
	public Pizza orderPizza(String type) {
     
		Pizza pizza;
 
		pizza = factory.createPizza(type);
 
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

}

有人会觉得这是个障眼法,实际上不过是把new的操作放到了另一个对象中,要修改类的时候,还是得不停地去修改那些if-else语句,不过是换了一个地方修改,这有什么意义呢?

小明表示,别忘了,我们的工厂可以有很多的客户,我们可能有个快递系统,也要创建披萨,假如所有的客户都要自己去new实例化这披萨,那么修改起来会更加麻烦。我们给所有的客户提供工厂创建接口,将所有的new实例化代码全部从客户程序删除。要修改实例化代码,只需要集中到工厂中修改就好了。

简单工厂并不是一个设计模式,只是一种编程习惯,即将创建对象A的任务放到简单工厂B中,以后创建A时候都调用简单工厂B就行了。接下来我们将讲讲两个重量级的设计模式。

工厂方法模式

披萨店老板在小明的设计下,击败了对手,在全国开起了连锁店。现在分店开到了纽约,芝加哥,加州。每个地方的披萨口味都不一样。
假如还是按照之前的简单工厂方法。那就要新建三个工厂。但是这么做未免太过繁琐,这三个工厂仅仅是披萨的味道不一样,加工流程都是一样的。小明想,我能不能从中抽出不变的部分,让它们继承同一个超类呢?说干就干,小明决定新建一个抽象类PizzaStore,将creatPizza()方法设为抽象。

Head First 设计模式笔记 4.工厂模式_第2张图片
图2 修改后的工厂

下面是他改造的抽象披萨店的代码

public abstract class PizzaStore {
     
 
	abstract Pizza createPizza(String item);
 
	public 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;
	}
}

实现纽约披萨店的代码

package headfirst.designpatterns.factory.pizzafm;

public class NYPizzaStore extends PizzaStore {
     

	Pizza createPizza(String item) {
     
		if (item.equals("cheese")) {
     
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
     
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
     
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
     
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

这里实际上用到了新的设计模式——工厂方法模式。工厂模式中,有两个角色

  • 抽象创建者类,定义了抽象的工厂方法,让子类去实现它制造产品。如PizzaStore,它提供的工厂方法是orderPizza()
  • 具体创建者类,继承了抽象创建者类,实现了工厂方法。如NYPizzaStore

工厂方法模式定义了创建对象的接口,但由子类决定具体要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

Head First 设计模式笔记 4.工厂模式_第3张图片
图3 工厂模式图解

依赖倒置原则

让我们来看看一个垃圾的披萨店设计

package headfirst.designpatterns.factory.pizzafm;

public class DependentPizzaStore {
     
 
	public Pizza createPizza(String style, String type) {
     
		Pizza pizza = null;
		if (style.equals("NY")) {
     
			if (type.equals("cheese")) {
     
				pizza = new NYStyleCheesePizza();
			} else if (type.equals("veggie")) {
     
				pizza = new NYStyleVeggiePizza();
			} else if (type.equals("clam")) {
     
				pizza = new NYStyleClamPizza();
			} else if (type.equals("pepperoni")) {
     
				pizza = new NYStylePepperoniPizza();
			}
		} else if (style.equals("Chicago")) {
     
			if (type.equals("cheese")) {
     
				pizza = new ChicagoStyleCheesePizza();
			} else if (type.equals("veggie")) {
     
				pizza = new ChicagoStyleVeggiePizza();
			} else if (type.equals("clam")) {
     
				pizza = new ChicagoStyleClamPizza();
			} else if (type.equals("pepperoni")) {
     
				pizza = new ChicagoStylePepperoniPizza();
			}
		} else {
     
			System.out.println("Error: invalid type of pizza");
			return null;
		}
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

这里我们在实例化不同的对象,实际上就是在依赖它的具体类。当这些具体类要修改的时候,就要修改依赖它的类。

Head First 设计模式笔记 4.工厂模式_第4张图片
图4 高度依赖的类

所以,在代码中,减少对具体类的依赖是我们的追求。这实际上就是“依赖倒置原则”所提倡的。

要依赖抽象,而不是具体类

听起来是不是很像“针对接口编程,不针对实现编程”,并不一样,这里更加强调的是抽象。它想要强调的是:不要让高层组件依赖低层组件。并且高层组件和低层组件都应该依赖于抽象。例如上文中,高层组件“PizzaStore”就依赖于各种低层组件披萨类。我们应该去重写代码以便于我们依赖于抽象类,而不依赖具体类。

现在就让根据这个原则改造一下代码,在上述代码中,我们需要自己去实例化每个具体的披萨类。我们可以用工厂方法把实例化的过程抽取出来。

Head First 设计模式笔记 4.工厂模式_第5张图片
图5 依赖置换后

那么为什么要叫依赖置换原则呢?我们置换了什么呢?

在以往的设计的流程中,我们要开一个披萨店,就要提供各种口味的披萨。这样披萨店就会牢牢依靠这些具体的披萨种类。后面的设计里,我们抛开具体的披萨种类,将所有口味的披萨抽象化。得到一个抽象的Pizza类。这样,我们的过去的高层组件(披萨店)依赖于底层组件(各种口味的披萨),现在,无论高层组件(披萨店)还是底层组件(各种口味的披萨)都依赖于抽象的Pizza类。

抽象工厂模式

现在披萨店已经具备了弹性的框架,并且遵循设计原则。只剩下具体的披萨没有设计了。这里,我们发现,披萨是由面团,酱料,芝士,佐料构成。而面团,酱料等组件有不同的种类。这些不同种类搭配出了不同口味的披萨。

分析一下,就可以发现,所有的披萨具有相同的组件,而这些披萨的组件有不同的实现。不同区域的披萨店实现了完整的原料家族。

  • 根据之前的工厂模式,我们建立一个抽象的原料工厂PizzaIngredientFactory。然后用具体制造的工厂实现它,用来生产不同的原料。
  • 把每种披萨的原料如面粉,酱料等设计为接口,让具体的类去继承这个接口。
  • 披萨店的具体实例作为抽象原料工厂PizzaIngredientFactory的客户。

我们设计出了这样的类图。

Head First 设计模式笔记 4.工厂模式_第6张图片
图5 抽象工厂类图

这里,我们邂逅了新的设计模式——抽象工厂模式,这个模式可以创建对象的家族,例如披萨,一套时装。PizzaIngredientFactory就是一个抽象工厂。

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

抽象工厂模式 VS 工厂方法模式

抽象工厂模式和工厂方法模式都用于创建对象,将创建对象这一任务单独抽出。但是它们的实现方法不同。

  • 工厂方法模式主要用继承,用具体类继承抽象类,覆盖它的工厂方法。这样,客户就只需要关心他们所使用的抽象类型就可以了,而不必去担心具体的类。一般适用于计划不同条件下创建不同实例时。
  • 抽象工厂模式主要使用组合,将一组产品集合为一个抽象类,然后让子类去实现它。主要用于创建一个产品家族的抽象类型。不过,当加入新的产品的时候,就需要修改接口。一般用于创建对象家族。

你可能感兴趣的:(Head,First设计模式,设计模式,软件架构,java)