二、工厂设计模式
1、简单工厂
简单工厂是静态工厂方法模式。简单工厂就是你给一个type,返回给你一个对象,整个创建过程可以放在工厂里面。
public class ResourceLoader {
public Resource load(String url) {
// 根据url获取前缀
String prefix = getPrefix(url);
Resource resource = null;
// 根据前缀获取资源
return ResourceFactory.create(prefix, url);
}
private String getPrefix(String url) {
if (null == url || "".equals(url) || !url.contains(":")) {
throw new ResourceLoadException("传入资源的url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
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");
}
}
}
如上面代码举例,将根据if-else创建实例的业务逻辑抽离出来,放到ResourceFactory工厂类中 。有了工厂类,我们可以将创建资源产品这个单一的能力赋予产品工厂,这样更符合单一原则。
简单工厂设计模式,提取一个工厂类,工厂会根据传入的不同参数类型,当需要某个产品时直接使用create创建就可以,不用关注具体的创建过程,具体好处如下:
(1)工厂将创建的过程进行封装,不需要关系创建的细节,更加符合面向对象思想;
(2)这样主要的业务逻辑不会被创建对象的代码干扰,代码更易阅读;
(3)产品的创建可以独立测试,更将容易测试;
(4)独立的工厂类只负责创建产品,更加符合单一原则。
2、工厂方法
如果有一天,我们的if分支逻辑不断膨胀,有变为肿瘤代码的可能,就有必要将if分支逻辑去掉,为了解决这个问题,工厂方法设计模式出现了。之前的简单工厂是一个大而全的工厂,一个工厂需要创建不同的产品,工厂方法讲究的是工厂也要专而精。
首先,我们需要将生产资源的工厂类进行抽象:
public interface IResourceLoader {
Resource load(String url);
}
具体的资源要实现这个抽象的接口:
public class ClassloaderResourceFactoryImpl implements IResourceLoader {
@Override
public Resource load(String url) {
// 省略复杂的创建过程...
return new Resource(url);
}
}
public class DefaultResourceFactoryImpl implements IResourceLoader {
@Override
public Resource load(String url) {
// 省略复杂的创建过程...
return new Resource(url);
}
}
public class FileResourceFactoryImpl implements IResourceLoader {
@Override
public Resource load(String url) {
// 省略复杂的创建过程...
return new Resource(url);
}
}
public class HttpResourceFactoryImpl implements IResourceLoader {
@Override
public Resource load(String url) {
// 省略复杂的创建过程...
return new Resource(url);
}
}
这样,之后我们只要想新增一种资源加载的方法,就去实现IResourceLoader 接口,工厂方法模式相比于简单工厂模式更符合开闭原则。
public class ResourceLoader {
String prefix = getPrefix(url);
ResourceLoader resourceLoader = null;
public Resource load(String url) {
// 2. 根据前缀选择不同的工厂,产生独自的产品
if ("http".equals(prefix)) {
resourceLoader = new HttpResourceFactoryImpl();
} else if ("file".equals(prefix)) {
resourceLoader = new FileResourceFactoryImpl();
} else if ("classpath".equals(prefix)) {
resourceLoader = new ClassloaderResourceFactoryImpl();
} else {
resourceLoader = new DefaultResourceFactoryImpl();
}
return resourceLoader.load(url);
}
private String getPrefix(String url) {
if (null == url || "".equals(url) || !url.contains(":")) {
throw new ResourceLoadException("传入资源的url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
但是,这样改造代码后,我们增加资源加载方法仍然需要增加if-else分支,不符合开闭原则,所以,进行如下改造:
public class ResourceLoader {
private static Map<String,IResourceLoader> resourceLoaderCache = new HashMap<>(8);
static {
resourceLoaderCache.put("http",new HttpResourceLoader());
resourceLoaderCache.put("file",new FileResourceLoader());
resourceLoaderCache.put("classpath",new ClassPathResourceLoader());
resourceLoaderCache.put("default",new DefaultResourceLoader());
}
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
return resourceLoaderCache.get(prefix).load(url);
}
private String getPrefix(String url) {
if (null == url || "".equals(url) || !url.contains(":")) {
throw new ResourceLoadException("传入资源的url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
通过加入缓存的方式,可以减去if-else的复杂分支逻辑,之后如果增加具体资源加载方法,只需要实现IResourceLoader接口,并在缓存中注册即可。
虽然已经极大的优化了我们的代码,但是我们发现,新增方法仍然需要,修改我们static中的源代码,还是有一点点不符合开闭原则,所以还可以用我们的配置文件进行优化:
首先,我们要搞一个配置文件,文件名为resourceLoader.properties。
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
public class ResourceLoader {
private static Map<String,IResourceLoader> resourceLoaderCache = new HashMap<>(8);
static {
// 通过输入流加载配置文件
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("resourceLoader.properties");
Properties properties = new Properties();
try {
// 调用properties的load方法将输入流加载到内存
properties.load(in);
for (Map.Entry<Object, Object> property : properties.entrySet()) {
String key = property.getKey().toString();
Class<?> clazz = Class.forName(property.getValue().toString());
IResourceLoader loader = (IResourceLoader)clazz.getConstructor().newInstance();
resourceLoaderCache.put(key, loader);
}
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
return resourceLoaderCache.get(prefix).load(url);
}
private String getPrefix(String url) {
if (null == url || "".equals(url) || !url.contains(":")) {
throw new ResourceLoadException("传入资源的url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
以后我们想新增或删除一个resourceLoader只需要写一个类实现IResourceLoader接口,并在配置文件中进行配置即可。此时此刻我们已经看不到if-else的影子了。
我们的代码中产品是简单单一的类,事实上,在工作中,我们的产品可能是及其复杂的,我们同样需要对整个产品线进行抽象,具体产品继承抽象类。
public abstract class AbstractResource {
private String url;
public AbstractResource(){}
public AbstractResource(String url) {
this.url = url;
}
protected void shared(){
System.out.println("这是共享方法");
}
/**
* 每个子类需要独自实现的方法
* @return 字节流
*/
public abstract InputStream getInputStream();
}
public class ClasspathResource extends AbstractResource {
public ClasspathResource() {
}
public ClasspathResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
public class HttpResource extends AbstractResource {
public HttpResource() {
super();
}
public HttpResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
public class FileResource extends AbstractResource {
public FileResource() {
super();
}
public FileResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
public class DefaultResource extends AbstractResource {
public DefaultResource() {
super();
}
public DefaultResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
而我们的具体的工厂也要做一些改变
public class ClassPathResourceLoader implements IResourceLoader {
@Override
public AbstractResource load(String url) {
// 中间省略复杂的创建过程
return new ClasspathResource(url);
}
}
// 其他工厂一样要做改变
3、抽象工厂
抽象工厂就是要抽象出一个产品族。就是一类产品有多种类型的产品,这里就不过多介绍了,之后如果大家感兴趣我会再更新的。