设计模式之工厂模式(一)

工厂模式的学习篇幅比较长,小编第一次看书的时候,就一口气花了一个多小时,还是通读。后面又断断续续地继续了解了下,力争做到清晰的认知,给大家一个简单的学习方式。

工厂模式的学习篇幅比较长,小编第一次看书的时候,就一口气花了一个多小时,还是通读。后面又断断续续地继续了解了下,力争做到清晰的认知,给大家一个简单的学习方式。所以,这次模块分的可能会比之前的多,涉及到多个工厂模式。好的,我们继续冲鸭!!!

除了使用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

爱生活,爱学习,爱感悟,爱挨踢