这篇博客记录了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;
}
然而过了几天,老板表示:“小明,时代又变了,有的披萨经营不善,我们不做了。我们又加入了一些新的披萨种类。”这意味着小明又得忙活了。
然而就算这次修改了代码,随着时间过去,菜单改变,代码必须一改再改。这里的代码没有对修改封闭。
|
问题出在哪里呢?小明仔细观察了一下代码,哦,原来问题出在了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;
}
}
没有new
的orderPizza
方法。再也无需再关心它所要制造什么种类的披萨,如果要修改披萨种类,只需要修改工厂代码就可以了。
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()
方法设为抽象。
|
下面是他改造的抽象披萨店的代码
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
工厂方法模式定义了创建对象的接口,但由子类决定具体要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
|
让我们来看看一个垃圾的披萨店设计
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;
}
}
这里我们在实例化不同的对象,实际上就是在依赖它的具体类。当这些具体类要修改的时候,就要修改依赖它的类。
|
所以,在代码中,减少对具体类的依赖是我们的追求。这实际上就是“依赖倒置原则”所提倡的。
要依赖抽象,而不是具体类
听起来是不是很像“针对接口编程,不针对实现编程”,并不一样,这里更加强调的是抽象。它想要强调的是:不要让高层组件依赖低层组件。并且高层组件和低层组件都应该依赖于抽象。例如上文中,高层组件“PizzaStore”就依赖于各种低层组件披萨类。我们应该去重写代码以便于我们依赖于抽象类,而不依赖具体类。
现在就让根据这个原则改造一下代码,在上述代码中,我们需要自己去实例化每个具体的披萨类。我们可以用工厂方法把实例化的过程抽取出来。
|
那么为什么要叫依赖置换原则呢?我们置换了什么呢?
在以往的设计的流程中,我们要开一个披萨店,就要提供各种口味的披萨。这样披萨店就会牢牢依靠这些具体的披萨种类。后面的设计里,我们抛开具体的披萨种类,将所有口味的披萨抽象化。得到一个抽象的Pizza类。这样,我们的过去的高层组件(披萨店)依赖于底层组件(各种口味的披萨),现在,无论高层组件(披萨店)还是底层组件(各种口味的披萨)都依赖于抽象的Pizza类。
现在披萨店已经具备了弹性的框架,并且遵循设计原则。只剩下具体的披萨没有设计了。这里,我们发现,披萨是由面团,酱料,芝士,佐料构成。而面团,酱料等组件有不同的种类。这些不同种类搭配出了不同口味的披萨。
分析一下,就可以发现,所有的披萨具有相同的组件,而这些披萨的组件有不同的实现。不同区域的披萨店实现了完整的原料家族。
PizzaIngredientFactory
。然后用具体制造的工厂实现它,用来生产不同的原料。PizzaIngredientFactory
的客户。我们设计出了这样的类图。
|
这里,我们邂逅了新的设计模式——抽象工厂模式,这个模式可以创建对象的家族,例如披萨,一套时装。PizzaIngredientFactory
就是一个抽象工厂。
抽象工厂模式提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确具体的种类。
抽象工厂模式和工厂方法模式都用于创建对象,将创建对象这一任务单独抽出。但是它们的实现方法不同。