设计模式 - 工厂方法模式
实例
先看一个例子
图片读取器
设计一个可以读取不同类型图片格式的程序,针对每种图片格式都需要一个图片读取器,如GIF读取器用于读取GIF
图片,JPG
读取器用于读取JPG
图片,需充分考虑系统的灵活性和可扩展性
简单工厂解决方案
- 使用简单工厂的解决方案大致如下:
Img.java
/**
* @Description 图片
*/
public abstract class Img {
/**
* 获取图片源
*/
public abstract void getSource();
}
GifImg.java
/**
* @Description GIF图片
*/
public class GifImg extends Img {
@Override
public void getSource() {
System.out.println("获取GIF图片源");
}
}
JpgImg.java
/**
* @Description JPG图片
*/
public class JpgImg extends Img {
@Override
public void getSource() {
System.out.println("获取JPG图片源");
}
}
ImgFactory.java
/**
* @Description 图片简单工厂类
*/
public class ImgFactory {
/**
* 简单工厂获取图片产品类
* @param args
* @return
*/
public static Img getImg(String args) {
Img img = null;
if (args.equalsIgnoreCase("gif")) {
img = new GifImg();
} else if (args.equalsIgnoreCase("jpg")) {
img = new JpgImg();
}
return img;
}
}
Test.java
/**
* @Description 图片简单工厂测试类
*/
public class Test {
public static void main(String[] args) {
Img img = ImgFactory.getImg("gif");
if (img == null) {
return;
}
img.getSource();
img = ImgFactory.getImg("jpg");
if (img == null) {
return;
}
img.getSource();
}
}
- 运行结果:
获取GIF图片源
获取JPG图片源
- 通过上述代码,简单工厂方法模式只提供一个工厂类,通过所传入的参数的不同来创建不同的产品,该工厂类处于对产品类进行实例化的中心位置,其最大的缺点就是当有新产品要加入系统中时必须修改工厂类的源代码,违背开闭原则,如上述代码需要加入一个
PNG
的产品类势必要修改ImgFactory
,如何实现增加新产品而不影响已有代码,接下来引出工厂方法模式,在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构
工厂方法模式
概念
- 工厂方法模式(
Factory Method Pattern
):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类 - 工厂方法模式又简称工厂模式(
Factory Pattern
)
工厂方法解决方案
Img.java
/**
* @Description 图片产品类
*/
public abstract class Img {
/**
* 获取图片源
*/
public abstract void getSource();
}
ImgFactory.java
/**
* @Description 图片工厂类(工厂方法模式)
*/
public interface ImgFactory {
/**
* 获取图片产品类
* @return
*/
Img getImg();
}
GifImg.java
/**
* @Description GIF图片产品类
*/
public class GifImg extends Img {
@Override
public void getSource() {
System.out.println("获取GIF图片源");
}
}
GifImgFactory.java
/**
* @Description Gif图片工厂类
*/
public class GifImgFactory implements ImgFactory {
@Override
public Img getImg() {
return new GifImg();
}
}
JpgImg.java
/**
* @Description JPG图片产品类
*/
public class JpgImg extends Img {
@Override
public void getSource() {
System.out.println("获取JPG图片源");
}
}
JpgImgFactory.java
/**
* @Description Gif图片工厂类
*/
public class JpgImgFactory implements ImgFactory {
@Override
public Img getImg() {
return new JpgImg();
}
}
Test.java
/**
* @Description 工厂方法模式测试类
*/
public class Test {
public static void main(String[] args) {
ImgFactory imgFetchFactory = new GifImgFactory();
Img imgFetch = imgFetchFactory.getImg();
imgFetch.getSource();
imgFetchFactory = new JpgImgFactory();
imgFetch = imgFetchFactory.getImg();
imgFetch.getSource();
}
}
- 输出如下:
获取GIF图片源
获取JPG图片源
- 类图如下:
- 工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法(
ImgFactory
),而由其子类(JpgImgFactory
、GifImgFactory
)来具体实现工厂方法,创建具体的产品对象,与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色
方案的演进(配置文件)
-
如上客户端
Test.java
调用代码还具有可改进的地方,可通过配置文件 + 反射实现在不修改客户端代码的基础上更换和增加新的图片读取方式 -
config.properties
factoryMethod.className=GifImgFactory
PropertiesUtil.java
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @Description Properties工具类
*/
public class PropertiesUtil {
/**
* 根据key读取value
* @Description: 相对路径, properties文件需在classpath目录下,
* 比如:config.properties在包com.coisini.util,
* 路径就是/com/coisini/util/config.properties
* @param filePath
* @param keyWord
* @return String
* @throws
*/
private static String getProperties(String filePath, String keyWord){
Properties prop = new Properties();
String value = null;
try {
InputStream inputStream = PropertiesUtil.class.getResourceAsStream(filePath);
prop.load(inputStream);
value = prop.getProperty(keyWord);
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
/**
* 根据配置文件提取类名返回实例对象
* @param filePath
* @param keyWord
* @param packagePath
* @return
*/
private static Object getBean(String filePath, String keyWord, String packagePath) {
try {
String className = getProperties(filePath, keyWord);
Class<?> c = Class.forName(packagePath + className);
return c.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取工厂方法实例对象
* @return
*/
public static Object getFactoryMethodBean() {
return getBean("/com/coisini/design/util/config.properties",
"factoryMethod.className",
"com.coisini.design.pattern.creational.factorymethod.v3.");
}
}
Test.java
/**
* @Description 工厂方法模式测试类(配置文件反射实现方式)
*/
public class Test {
public static void main(String[] args) {
ImgFactory imgFactory = (ImgFactory) PropertiesUtil.getFactoryMethodBean();
Img img = imgFactory.getImg();
img.getSource();
}
}
- 通过如上配置文件 + 反射实现方式,客户端代码无需使用new关键字来创建工厂对象,而是将具体工厂类的类名存在配置文件中,通过读取配置文件获取类名字符串,再使用反射机制根据类名字符串生成对象
总结
- 优点
1、用户只需关心所需产品对应的工厂,无需关心创建细节
2、加入新产品符合开闭原则,提高可扩展性
- 缺点
1、类的个数容易过多,增加复杂度
2、增加了系统的抽象性和理解难度
- 适用场景
1、创建对象需要大量重复代码
2、客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
3、一个类通过子类来指定创建哪个对象