一、设计模式概述
设计模式既不是Java代码,也不是API,而是一套被反复使用的,多数人知道的,经过分类编目的,抽象出来的代码设计经验总结。去掉这个句子的众多修饰词,即可得:设计模式是经验总结。
就像我们中学阶段做数学题目一样,老师总会强调的一点就是:不要光顾着刷题啊,要注意从相似的题目中总结出相同的方法和思想。设计模式便是如此,它是优秀的使用案例,使用它可提高代码的重用性,让代码易于理解,并保证代码的可靠性。
二、工厂模式的概念
我们从现实生活中的一个例子入手来帮助理解:我们知道,在现实生活中,几乎我们日常用到的所有产品都有相应的工厂来加工生产它们。那么工厂在这里扮演什么样的角色呢?其实就是给我们提供了一个对象实例供我们使用。理解工厂模式注意以下几点:
- 实例化对象,用工厂方法代替new操作
- 包括工厂方法模式和抽象工厂模式
- 抽象工厂模式是工厂方法模式的扩展
三、工厂模式的意图
- 通过定义一个接口创建对象,但是让子类决定哪些类需要被实例化
- 工厂方法把实例化工作推迟到子类去实现
四、工厂模式的应用场景
- 有一组类似的对象需要被创建
- 在编码时不能预见需要创建哪种类的实例
- 系统需要考虑扩展性,即应不依赖于如何被创建、组合、表达等细节问题
五、工厂模式的设计思想
在实际的开发中,我们总是会遇到一些易变的对象,但是我们希望其他一些依赖于这些易变对象的对象不随之改变而改变,这就要求我们:
- 尽量松耦合,一个对象的依赖对象变化与本身无关
- 实际产品与客户端剥离,责任分割
由于涉及模式本来就是一套抽象的方法总结,下面我们用软件工程里面的类图来表示工厂方法模式:
如上图所示,当客户端发出请求(或命令)需要创建一个对象的时候,这个请求将被传送creator(即工厂),然后通过一个封装好的接口来实现对象的创建。而抽象工厂模式即为对上述类图的扩展,在抽象工厂模式中,一个工厂会生产多种类型的的产品,当多个工厂参与时,便可以形成一个个产品族。
用一个实际例子来说明,相信大家都用过制作脸萌的相关软件。显然,在对脸萌这款应用进行编码的时候,我们是不知道要创建哪些对象的。在脸萌中,我们可以通过对发型、眉毛、鼻子、嘴巴、装饰等的选择来组成一个完整的人形象,这些都可以看作是不同类型的产品。而对于每一种产品,比如上述的鼻子,脸萌中也提供了多种多样的鼻子样式来供用户选择,这样就形成了产品族。
六、编码实例
下面用一个简单的实例来说明工厂方法模式。考虑这样一个场景:假设我们的客户端要根据实际情况产生两个产品,一个为蓝色墨水钢笔,一个为黑色墨水钢笔。(当然也可以是其他任何类型的产品)
1、根据上述工厂模式,我们首先实现一个接口:PenInterface.java
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 钢笔接口
*/
public interface PenInterface {
//生产了钢笔
public void produce();
}
2、然后定义蓝色钢笔类和黑色钢笔类:BluePen.java 以及 BlackPen.java:
//BluePen.java
package com.bebdong.Factory_mode;
/**
* @author Administrator
*
* 蓝色钢笔类
*/
public class BluePen implements PenInterface {
//实现接口中的钢笔生成方法
@Override
public void produce() {
System.out.println("------生产了一只蓝色钢笔--------");
}
}
//BlackPen.java
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 黑色钢笔类
*/
public class BlackPen implements PenInterface {
//实现接口中的钢笔生产方法
@Override
public void produce() {
System.out.println("------生产了一只蓝色钢笔--------");
}
}
3、最后来模拟客户端:Client.java
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 模拟客户端
*/
public class Client {
public static void main(String[] args) {
PenInterface bluePen=new BluePen();
bluePen.produce();
PenInterface blackPen=new BlackPen();
blackPen.produce();
}
}
小结1:我们可以看到,若利用上述的实现方式,那么每生产一个产品则需要在客户端显示地调用,既不利于维护,也不利于管理,这显然违背了我们工厂模式的原则。
5、建立一个“钢笔工厂”用于管理钢笔生产:PenFactory.java
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 钢笔共产,用于产生钢笔对象
*/
public class PenFactory {
public PenInterface producePen(String key){
if("blue".equals(key))
return new BluePen();
else if("black".equals(key))
return new BlackPen();
else
return null;
}
}
对客户端Client.java做相应的修改:
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 模拟客户端
*/
public class Client {
public static void main(String[] args) {
//PenInterface bluePen=new BluePen();
//bluePen.produce();
//PenInterface blackPen=new BlackPen();
//blackPen.produce();
PenFactory penFactory=new PenFactory();
PenInterface blue=penFactory.producePen("blue");
if(blue!=null)
blue.produce();
}
}
小结2:现在我们看到,创建钢笔对象只需要通过“钢笔工厂”即可。但是,仍然有一个不可忽视的问题,那就是如果有多种钢笔产品,那么钢笔工厂里面就会出现大量“if……else if……”的判断结构,这也不利于代码的维护。
6、我们利用Java提供的反射机制来创建实例。反射可以根据类的名称来创建实例。对PenFactory.java做如下修改:
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 钢笔共产,用于产生钢笔对象
*/
public class PenFactory {
//生产对象
public PenInterface producePen(String key){
if("blue".equals(key))
return new BluePen();
else if("black".equals(key))
return new BlackPen();
else
return null;
}
//根据类名生产对象
public PenInterface producePenByName(String className){
try {
PenInterface penInterface=(PenInterface) Class.forName(className).newInstance();
return penInterface;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
同理,客户端Client.java做相应修改:
package com.bebdong.Factory_mode;
/**
* @author Administrator
* 模拟客户端
*/
public class Client {
public static void main(String[] args) {
//PenInterface bluePen=new BluePen();
//bluePen.produce();
//PenInterface blackPen=new BlackPen();
//blackPen.produce();
PenFactory penFactory=new PenFactory();
//PenInterface blue=penFactory.producePen("blue");
//if(blue!=null)
//blue.produce();
PenInterface blue=penFactory.producePenByName("com.bebdong.Factory_mode.BluePen");
blue.produce();
}
}
运行客户端得到如下正确结果:
小结3:至此,我们利用工厂模式的方法,不断改写最初的代码设计,最终完成了一段模拟钢笔对象生产的过程。可以发现,在最后的版本中,基本实现了工厂方法模式的基本原则。
但是还有一些局部问题可以优化,比如:在Client.java中想要创建对象时,需要写出类的全路径名称,这个名称很长,当存在大量不同产品时,这个工作是很麻烦的,如果只用给一个key值就可以创建相应对象的话就会方便很多。我们可以用Java提供的.properties文件来保存这样的键值对,通过定义一个.properties文件读取方法就可以实现了。有兴趣的读者可以自己实现试试哦。
七、工厂方法的模式好处
通过上面的分析,我们可以看到,工厂方法模式的一个显著优势就在于解耦和。比如如果创建对象我们用pen=new Pen()的这种形式,当我们需要修改时,就要修改大量的地方,但是我们通过一个“钢笔工厂”来对其进行管理的,就可以只需修改工厂代码,其他地方引用工厂即可,即实现了解耦和。在涉及到海量代码的程序设计中,采用工厂方法的优势是显著的。