1.工厂的使用场景和解决的问题
工厂模式是一种建造模式,是对象创建的一种范式,它通过将对象创建的逻辑封装到工厂类中,来降低客户端代码的复杂性,使客户端不用过度关注对象创建的细节问题。使用工厂模式可以让客户端在需要创建对象时,只需要依赖相关的工厂即可,而无需依赖繁多的被创建的对象。因为存在工厂这个中间层,可以实现 对象创建逻辑的变化,对客户端无感知,实现客户端在创建对象的过程中,面向工厂而非具体对象编程。
为了方便下面的讨论,我们举例如下:客户端需要根据key获取对应的value值,k-v键值对可以存放在多种存储系统中,包括但不限于db,redis,file三种。
在不使用工厂模式时,对象创建耦合在客户端代码中。根据key获取value的具体代码如下:
public String getValueWithoutFacory(String storeType) {
IStore store = null;
if ("db".equalsIgnoreCase(storeType)) {
store = new DBStorage();
} else if ("file".equalsIgnoreCase(storeType)) {
store = new FileStore();
} else if ("redis".equalsIgnoreCase(storeType)) {
store = new RedisStore();
} else {
throw new IllegalArgumentException(
"store config type is not supported: " + storeType);
}
String key = "xxx_key";
String value = store.getValue(key);
return value;
}
从上面的代码来看,客户端本来只需要根据key获取value,结果大部分代码都是在创建需要的IStore对象,有点本末倒置了。
2.工厂分类
工厂模式有2中常用的变种:简单工厂和工厂方法。
(1)简单工厂
简单工厂就是将客户端创建对象的逻辑直接搬到工厂类中,也即是将客户端代码中对象创建的复杂度转移工厂类中。
public static IStorager getStorageWithoutCache(String storeType) {
IStore store = null;
if ("db".equalsIgnoreCase(storeType)) {
store = new DBStorage();
} else if ("file".equalsIgnoreCase(storeType)) {
store = new FileStore();
} else if ("redis".equalsIgnoreCase(storeType)) {
store = new RedisStore();
} else {
throw new IllegalArgumentException(
"store config type is not supported: " + storeType);
}
return store;
}
如果被创建的对象可以复用的话,那么可以将被创建的对象缓存起来,减少对象创建的性能和内存消耗。
private static Map<String, IStorager> map = new HashMap<String, IStorager>();
static {
map.put("db",new DBStorage());
map.put("file",new FileStore());
map.put("redis",new RedisStore());
}
public static IStore getStorage(String storeType) {
IStore store = map.get(storeType);
if (null == store) {
throw new IllegalArgumentException(
"store config type is not supported: " + storeType);
}
return store;
}
(2)工厂方法
工厂方法相对简单工厂来说会更复杂一些。他主要用来解决简单工厂中,当被创建的对象不可复用时,出现的大量 if-else 问题。工厂方法的主要实现思路是:为每种类型的对象创建一个单独的工厂,该工厂只创建该类型的对象。具体代码如下:
public class DBStoreFactory implements IStoreFactory{
@Override
public IStore createStore() {
return new DBStorage();
}
}
public class FileStoreFactory implements IStoreFactory{
@Override
public IStore createStore() {
return new FileStore();
}
}
public class RedisStoreFactory implements IStoreFactory{
@Override
public IStore createStore() {
return new RedisStore();
}
}
不过客户端在使用工厂方法时还是会出现大量 if-else 的问题,具体代码如下:
public String getValueWithFactoryMethod(String storeType) {
IStoreFactory storeFactory = null;
if ("db".equalsIgnoreCase(storeType)) {
storeFactory = new DBStoreFactory();
} else if ("file".equalsIgnoreCase(storeType)) {
storeFactory = new FileStoreFactory();
} else if ("redis".equalsIgnoreCase(storeType)) {
storeFactory = new RedisStoreFactory();
} else {
throw new IllegalArgumentException(
"store config type is not supported: " + storeType);
}
IStore store = storeFactory.createStorage();
String key = "xxx_key";
String value = store.getValue(key);
return value;
}
这个和不使用工厂模式时存在的问题是相同,只不过,不使用工厂模式的方式,if-else 出现在创建IStroe时,而使用工厂方法的时候,if-else出现在创建IStoreFactory。针对简单工厂出现的if-else问题,我们可以再次采用简单工厂的方式,只不过这个简单工厂,是工厂的工厂。
到这里你可能会产生疑问:首先我们使用工厂方法就是为了解决简单工厂中出现大量if-else的问题,而使用工厂方法虽然解决了简单工厂中if-else的问题,但是工厂方法本身也带来了if-else问题。那么我们再使用简单工厂去解决工厂方法的if-else的问题,那岂不是就陷入了恶性循环了吗?其实并不会,首先我们第一轮使用简单工厂时,之所以会引入if-else的问题,是因为简单工厂创建的对象,存在不可复用的问题。而在第二轮使用简单工厂创建的对象,本身就是一个工厂,工厂肯定是可以复用的,也就不会再出现if-else的问题。
具体代码如下:
public class StoreFactoryMap {
private static Map<String,IStoreFactory> factoryMap = new HashMap<>();
static {
factoryMap.put("db",new DBStoreFactory());
factoryMap.put("file",new FileStoreFactory());
factoryMap.put("redis",new RedisStoreFactory());
}
public static IStoreFactory getStoreFactory(String factoryType){
IStoreFactory iStoreFactory = factoryMap.get(factoryType);
if (null == iStoreFactory) {
throw new IllegalArgumentException(
"store config type is not supported: " + iStoreFactory);
}
return iStoreFactory;
}
3.工厂方法的lambda方式优化
上面的工厂方法,虽然可以解决简单工厂中对于不可复用对象创建过程中的多个if-else,但是会使代码的复杂度提高很多,通过观察工厂方法的实现,我们发现,工厂方法中:针对每种类型的对象,都会定义一个工厂类,这些工厂类功能都很简单,其内部只定义了一个方法,方法做的事情就是创建对象,因此可以用一个函数式接口来替换定义的多个工厂类,降低代码层面实现的复杂度。
具体代码如下:
public class StoreFactoryMapOpt {
private static Map<String, Supplier<IStore>> map = new HashMap<>();
static {
map.put("db",()->new DBStore());
map.put("file",()->new FileStore());
map.put("redis",()->new RedisStore());
}
public static IStore getStoreFactory(String factoryType){
Supplier<IStore> iStoragerSupplier = map.get(factoryType);
if (null == iStoragerSupplier) {
throw new IllegalArgumentException(
"store config type is not supported: " + factoryType);
}
return iStoragerSupplier.get();
}
public String getValueWithFactoryMapOpt(String storeType) {
IStore iStore = StoreFactoryMapOpt.getStoreFactory(storeType);
String key = "xxx_key";
String value = iStore.getValue(key);
return value;
}
}
4.工厂模式使用的思考
使用工厂模式,主要为了降低客户端创建对象的复杂度。但是复杂度本身并没有因为使用工厂而消失,只是从客户端转移到了工厂,而是用工厂方法,可以降低简单工厂的复杂度,但是仍然没有将复杂度消除,只是将复杂度转移到了工厂的工厂。那么我们此时再回过头来看,使用简单工厂 和 工厂方法,需要我们创建大量的且与业务本身无关的工厂相关的类,带来的复杂度,和没有使用工厂时,仅有的几个if-else带来的复杂度相比,哪个更复杂?你品,你细品。