题记
顾客:老板,给我来个对象。
工厂:好嘞。
概述
常用的
工厂模式有3种:简单工厂模式,工厂模式,抽象工厂模式。它们的作用从名字就可以看出:就是制造出对象返回给客户端(即需要对象的地方)。大多数时候我们需要对象时都是直接使用new来创建对象实例的,这样必然造成代码中各个类之前相互依赖(即变量持有具体类的引用),使得代码内部耦合度过高。反应出来的问题就是当某个地方需要修改时,就会修改一大片代码甚至包括客户端代码,这违背了一个重要软件设计原则:开闭原则(对扩展开放,对修改封闭)。下面看一个例子:在一个年终总结大会上,需要每个人都发言,回顾一下过去,展望一下未来,于是就需要很多不同类型的对象实例。通常,我们的做法是直接在客户端new一个实例对象,然后调用该对象的方法。如下:
类的关系
上图中Person类为抽象类,2个子类实现抽象方法speak。
Person代码:
public abstract class Person {
public abstract void speak();
}
2个人物代码:
public class Coder extends Person {
@Override
public void speak() {
System.out.println("老板,说好的程序员鼓励师呢?");
}
}
public class Boss extends Person {
@Override
public void speak() {
System.out.println("只要大家好好工作,明年我就请程序员鼓励师!");
}
}
有了人物,接下来就可以开总结大会了,代码如下:
//总结大会
public class Summary {
Person person;
//大会发言
private void summarySpeak(String type){
if("coder".equals(type)){
person = new Coder();
}else if("boss".equals(type)){
person = new Boss();
}
person.speak();
}
}
那么问题来了:HR表示我也要发言。这个时候就需再创建一个基类为Person类的HR子类(这里还只是扩展),然后修改Summary类中大会发言的判断代码加上HR的判断(修改了客户端代码),违背了开闭原则(可以想象只要有新角色加入,就得修改客户端代码)。这时候就该工厂模式闪亮登场了。
一、简单工厂模式
软件编程的一个重要原则是:封装改变,把变化的部分分离出去。既然问题在于发言角色会经常变化,为什么不单独创建一个
类
(简单工厂类)来专门生成我想要的角色呢?
继续添加新角色:
Hr代码:
public class Hr extends Person {
@Override
public void speak() {
System.out.println("大家有话好好说,请不要跳槽!!!");
}
}
SimpleFactory代码:
public class SimpleFactory {
//创建角色
public Person getPerson(String type){
if("coder".equals(type)){
return new Coder();
}else if("boss".equals(type)){
return new Boss();
}else if("hr".equals(type)){
return new Hr();
}
return null;
}
简单工厂
可以看出,Summary类里面持有2个引用:person和simpleFactory,其中person是通过simplFactory获取的。我们把创建角色的代码移到了简单工
厂类里面,Summary类里面只是持有简单工厂的引用,当需要创建角色时,直接调用该引用的
getPerson方法即可。
这样虽然以后有新角色加入,我们
还是得修改SimpleFactory这个类里面的代码,但是
请注意,这个时
候我们不再需要去修改客
户端里面的代码了(即Summary类里面的代码),
因为
Summary里面只是
单存地调用
SimpleFactory里面的getPerson方法获取角色实例。其实这里的修改
已经可以理解为是一种扩展了。
以上就是简单工厂的使用,简单来说就是:把需要创建新对象的部分封装到一个单独的工厂类中(即new发生在简单工厂类中),客户端直接调用
工厂的创建对象方法获取实例。好处是把客户端代码解耦,减少对实例类的依赖,以后的修改也只是修改工厂类而已,不影响客户端代码。
二、工厂模式
工厂模式
从上图可以看出,工厂模式特点是:把创建对象的任务交给了创建者的子类,由子类角色到底要创建什么产品。父类可以是一个接口或者抽象类。
那么这样做有什么意义呢?他对扩展封闭对修改开放怎么体现的呢?在哪些地方做到解耦了呢?
其实可以这么想:既然创建对象的任务交给了子类,那么父类(
此时即为
抽象类)
和客户端自然不再关心到底是什么具体类型(这就体现了解耦
),它们只需要调用抽象的getProduct方法获取对象(实际创建的是什么对象是子类的责任,我不关心),然后调用该对象的方法完成操作即可。
我们还是举上面的例子来说:
总结大会工厂模式类图
PersonFactory代码:
public abstract class PersonFactory {
Person person;
//总结大会开始
public void summarySpeak(){
person = getPerson();
person.speak();
}
//创建角色
public abstract Person getPerson();
}
BossFactory代码:(其他2个类似)
public class BossFactory extends PersonFactory {
@Override
public Person getPerson() {
return new Boss();
}
}
可以看到父类中的得获取person的方法为抽象的,交给了子类来具体创建,父类只管开总结大会,即调用person的speak方法发言即可(因为开会
中每个人任务都是一样的:发言,所以这里把开会放到了父类中,即使以后需要每个人唱歌也很好扩展,直接加上person.sing()即可)。子类则负
责具体创建哪个角色,它们真正决定了每个人怎么发言。即使以后有新角色加入,或者Boss的发言改变了,也不需要修改父类中的代码。
以上就是工厂模式的应用,简单来说,工厂模式就是把创建对象的任务交给了子类执行(由继承实现),父类和客户端不需要关系到底是创建的
什么类型,它们只
要调用获得的类型的方法完成自己所关心的任务即可(如上面的完成总结大会),这就是解耦的一种体现,也是一种针对抽象编程
的体现。
三、抽象工厂模式
抽象工厂模式
从上图可以看出:抽象工厂模式其实是对工厂模式的扩展,工厂模式是要创建一种类型的对象(如上面只是创建Person类的不同子类),而抽象工
厂模式是要创建多种类型的对象,然后把这些对象组合起来使用。如上图,创建对象的任务依然交给了子类来觉得,但是子类里面需要决定创建多种不
同具体的产品(如ProductA的1还是2,ProductB的1还是2)。
回到公司开总结大会的例子上来,显然只是发一个言是不能满足一个总结大会的要求的,大伙儿最期待的还是领取年终奖。所以加入领取年终奖的
环节。这个类图如下:
公司开年会
可以看到开年会doSummary被放到抽象类CommpanySummary里面,里面就是调用person.speak()和prize.getPrize()。而每个部门都需要开年会,
发言和发奖品也有差异,所以每个部门就是一个工厂,它们创建的是一堆对象,这些对象来决定抽象类CommpanySummary里面的doSummary怎么进行:
谁发言,怎么发奖品等。
抽象工厂可以看成是工厂模式的扩展,或者说抽象工厂里面包含了工厂模式。抽象工厂主要是创建一堆对象,把这些对象组合起来完成一个功
能。而每一个对象的创建又可以看出是工厂模式的体现。
四、结束语
整个工厂模式其实都是对变化的封装,面向抽象编程,把具体类型和客户端解耦,很好地体现了开闭原则。工厂模式的主要功能就是创建对象,
根据
创建对象的方式,地点不同又可以分为:
简单工厂模式,工厂模式,抽象工厂模式。
五、参考资料
1.《Head First设计模式》