一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。
1、简单工厂
简单工厂叫作静态工厂方法模式(Static Factory Method Pattern)。
现在有一个场景,我们需要一个资源加载器,他要根据不用的url进行资源加载 ,但是如果我们将所有的加载实现代码全部封装在了一个load方法中,就会导致一个类很大,同时扩展性也非常差,当想要添加新的前缀解析其他类型的url时,发现需要修改大量的源代码,我们的代码如下:
java
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Resource {
private String url;
}
public class ResourceLoadException extends RuntimeException{
public ResourceLoadException() {
super("加载资源是发生问题。");
}
public ResourceLoadException(String message) {
super(message);
}
}
public class ResourceLoader {
public Resource load(String filePath) {
String prefix = getResourcePrefix(filePath);
Resource resource = null;
if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂
return new Resource(url);
} else if ("file".equals(type)) {
// ..建立流,做异常处理等等
return new Resource(url);
} else if ("classpath".equals(type)) {
// ...
return new Resource(url);
} else {
return new Resource("default");
}
return resource;
}
private String getPrefix(String url) {
if(url == null || "".equals(url) || !url.contains(":")){
throw new ResourceLoadException("此资源url不合法.");
}
String[] split = url.split(":");
return split[0];
}
}
在上边的案例中,存在很多的if分支,如果分支数量不多,且不需要扩展,这样的编写方式当然没错,然而在实际的工作场景中,我们的业务代码可能会很多,分支逻辑也可能十分复杂,这个时候简单工厂设计模式就要发挥作用了。
我们可以创建一个工厂,让工厂去根据我们的url生成指定的资源。
java
public class ResourceFactory {
public static Resource create(String type,String url){
if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂
return new Resource(url);
} else if ("file".equals(type)) {
// ..建立流,做异常处理等等
return new Resource(url);
} else if ("classpath".equals(type)) {
// ...
return new Resource(url);
} else {
return new Resource("default");
}
}
}
这就是简单工厂设计模式,提取一个工厂类,工厂会根据传入的不同的类型,创建不同的产品。
好处如下:
1、工厂将创建的过程进行封装,不需要关系创建的细节,更加符合面向对象思想
2、这样主要的业务逻辑不会被创建对象的代码干扰,代码更易阅读
3、产品的创建可以独立测试,更将容易测试
4、独立的工厂类只负责创建产品,更加符合单一原则
2、工厂方法
如果有一天,我们的if分支逻辑不断膨胀,就有必要将 if 分支逻辑去掉,那又该怎么办呢?比较经典的处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。我们会为每一个 Resource 创建一个独立的工厂类,形成一个个小作坊,将每一个实例的创建过程交给工厂类完成,重构之后的代码如下所示:
java
public interface IResourceFactory {
Resource create(String url);
}
public class ClasspathResourceFactory implements IResourceFactory {
//职责单一,工厂里再拆分出小作坊,独立生产某一种资源
@Override
public Resource create(String url) {
//中间省略200行代码...
return new Resource(url);
}
}
public class DefaultResourceFactory implements IResourceFactory {
@Override
public Resource create(String url) {
//中间省略200行代码...
return new Resource(url);
}
}
public class FtpResourceFactory implements IResourceFactory {
//职责单一,工厂里再拆分出小作坊,独立生产某一种资源
@Override
public Resource create(String url) {
//中间省略200行代码...
return new Resource(url);
}
}
实际上,这就是工厂方法模式的典型代码实现。这样当我们新增一种读取资源的方式时,只需要新增一个实现,并实现 IResourceFactory 接口即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。
当然有人就会说了这有什么用能呢,到时候使用的时候还不是需要如下的方式吗?这个工厂不就是来添乱的吗?
java
if(type.equals("http")){
resourceFactory = new HttpResourceFactory();
}else if(type.equals("ftp")){
resourceFactory = new FtpResourceFactory();
}else if(type.equals("classpath")){
resourceFactory = new ClasspathResourceFactory();
}else{
resourceFactory = new DefaultResourceFactory();
}
return resourceLoader.create();
这样做至少在代码阅读层面上变得更容易阅读了。一个类不至于那么臃肿,但是我们还可以继续优化。
我们为每个产品引入了工厂,却发现需要为创建工厂这个行为付出代价,在创建工厂这件事上,仍然不符合开闭原则,为了解决上述的问题我们又不得不去创建一个工厂的缓存来统一管理工厂实例,以后使用工厂会更加的简单,代码如下:
java
private static Map<String, IResourceFactory> resourceFactoryMap = new HashMap<>(8);
//版本二
static {
resourceFactoryMap.put("http", new HttpResourceFactory());
resourceFactoryMap.put("ftp", new FtpResourceFactory());
resourceFactoryMap.put("default", new DefaultResourceFactory());
resourceFactoryMap.put("classpath", new ClasspathResourceFactory());
}
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
return resourceLoaderCache.get(prefix).create(url);
}
当然你如果觉得还是不够,你觉得修改需求还是不够灵活,仍然需要修改static中的代码,我们可以这样做,搞一个配置文件如下,将我们的工厂类进行配置,如下:
XML
http=com.ydlclass.factoryMethod.resourceFactory.impl.HttpResourceLoader
file=com.ydlclass.factoryMethod.resourceFactory.impl.FileResourceLoader
classpath=com.ydlclass.factoryMethod.resourceFactory.impl.ClassPathResourceLoader
default=com.ydlclass.factoryMethod.resourceFactory.impl.DefaultResourceLoader
这样我们可以在static中这样编写代码,让它完全满足开闭原则:
java
//版本三
//完全满足开闭原则,当需要扩展需求时,只需要新增实现和修改配置文件,不需要修改一行代码
static {
InputStream inputStream = ClassLoader.getSystemClassLoader()
.getResourceAsStream("resourceFactory.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String key = entry.getKey().toString();
Class<?> clazz = Class.forName(entry.getValue().toString());
IResourceFactory iResourceFactory = (IResourceFactory) clazz.getConstructor().newInstance();
resourceFactoryMap.put(key, iResourceFactory);
}
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
以后我们想新增或删除一个resourceLoader只需要写一个类实现IResourceLoader接口,并在配置文件中进行配置即可。此时此刻我们已经看不到if-else的影子了。
3、小结
工厂方法简单来说,就是在之前的简单工厂的基础上,在一个大工厂里面再细分出来许多小作坊 ,每个小作坊一般职责单一,只负责生产自己特定的产品,更符合开闭原则,提高代码的可扩展性和灵活性。