工厂模式的学习篇幅比较长,小编第一次看书的时候,就一口气花了一个多小时,还是通读。后面又断断续续地继续了解了下,力争做到清晰的认知,给大家一个简单的学习方式。所以,这次模块分的可能会比之前的多,涉及到多个工厂模式。好的,我们继续冲鸭!!!
除了使用new操作符之外,还有更多制造对象的方法。我们将了解到实例化这个活动不应该总是公开地进行,也会认识到初始化经常会造成“耦合”问题。所以,这肯定不是我们希望的这样对吧?继续学习下去,我们将了解工厂模式如何从复杂的依赖中帮你脱困。
当看到“new”,就会想到“具体”
很多朋友应该有一个疑惑,之前说的原则,不应该针对实现编程,但是当我们每次使用new的时候,其实就是在针对实现编程呀。使用了“new”,就是在实例化一个具体类,所以用的的确是实现,而不是接口。遇到一个类的情况,还好说,但是遇到多个类,就必须等到运行时,才知道该实例化哪一个。
Duck duck;
if(picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}
这段代码如果一旦有变化扩展,就必须重新打开这段代码进行检查和修改,势必会造成部分系统更难维护和更新,也更容易犯错。
“new”有什么不对劲
针对Java程序来说,new是最最基础的部分了,所以从技术上来说,new丝毫没有问题,问题的关键在于经常要进行的改变。
针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。为啥呢?如果代码是针对接口编程,那么通过多态可以与任何新类实现该接口。但是,当代码使用大量的具体类时,这就很麻烦了,就必须对代码进行改变。也就是说,你的代码并非“对修改关闭”。想用新的具体类型来扩展代码,就必须重新 打开它。
所以,有没有解决办法呢?还记得我们的第一个原则不,就是用来改变,并帮我们“找出会变化的方面,把它们从不变的部分分离出来”。
之前的装饰者模式,我们喝了可口的咖啡,那么在工厂模式里,就让我们给咖啡加点搭配,来尝尝披萨的口味吧。
识别变化的方面,以及你的初步判断
假设你有一个披萨店,为了让系统有弹性,很是希望这是一个抽象类或接口。但如果这样,这些类或接口就无法直接实例化了。
Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza
}
所以,我们还是需要增加一些代码,来“决定”适合披萨的类型,然后再“制造”披萨:
public Pizza orderPizza(String type) {
Pizza pizza;
// 根据披萨的类型,我们实例化正确的具体类,然后将其赋值给pizza实例化变量。
// 请注意,这里的任何披萨都必须实现Pizza接口
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
}
// 一旦我们有了披萨,需要做一些必要的工作。每个Pizza的子类型都知道如何准备自己
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是压力来自增加更多的披萨类型
但是,每个产业都会存在竞争对手的,是吧。当其他的披萨店开发出了新产品,你怎么办呢。比如人家有了Clam Pizza、Veggie Pizza。还能怎么办,必须与时俱进,与对手一同进步,把这些披萨加入到你的店里,顺带淘汰一些过时了的披萨。
完蛋了,如果要增加披萨,又要去淘汰过时的披萨,在程序的世界里就是实例化某些类,删除某些实例化的类。orderPizza()出问题了,我们无法让orderPizza()对修改关闭;所以,我们用到了第一节学到的封装,我们要封装这些增删改的东西。
封装创建对象的代码
那么封装什么才是符合我们预期的呢,显而易见,现在最好将创建对象移到orderPizza()之外。但怎么做呢?我们要把创建披萨的代码移到另一个对象中,由这个对象专职创建披萨。
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
}
就是把这个移走,新建一个对象,这个新对象只管如何创建披萨。如果任何对象想要创建披萨,直接就找这个即可。
我们称这个新对象为“工厂”,并建造工厂
工厂(factory)处理创建对象的细节,一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。现在,我们方式很简单了,当你想要一个披萨的时候,你就叫披萨工厂去做一个就好了。orderPizza()方法只关心从工厂得到了一个披萨,而这个披萨实现了Pizza接口,所以他可以调用prepare()、bake()、cut()、box()来分别进行准备、烘烤、切片、盒装。
那我们把这个工厂建造起来吧,还不赶紧的。
// 这个工厂只做一件事,帮他的客户创建披萨
public class SimplePizzaFactory {
// 在工厂内定义一个方法createPizza()方法,所有客户用这个方法来实例化新对象
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类需要把这个工厂加进来,毕竟我们什么事情都交给工厂去完成了。修改如下:
// 你需要更多的披萨类型传入orderPizza()
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
// 一旦我们有了披萨,需要做一些必要的工作。每个Pizza的子类型都知道如何准备自己
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
定义简单工厂
简单工厂其实不是一个设计模式,反而比较像是一种变成习惯。但由于经常被使用,所以在书中,给他一个“Head First Pattern荣誉奖”。有些开发人员的确是把这个编程习惯误认为是“工厂模式”(Factory Pattern)。但是不要因为简单工厂不是一个设计模式,就忽略了他。让我们来看看新的披萨店的类图:
好啦,工厂模式的热身结束啦。谢谢简单工厂模式给我们暖身,之后我们会进入两个重量级的模式,他们都是工厂。所以,别担心你吃不到披萨,后面还会有更多的披萨呢。
再提醒一次,在设计模式中,所谓的“实现一个接口”并“不一定”表示“写一个类,并利用implement关键词来实现某个Java接口”。“实现一个接口”泛指“实现某个超类型(可以使类或接口)的某个方法”。
简单工厂你get了吗?
推荐阅读:
GitHub地址 HeadFirstDesign