一般工厂模式有三种,简单工厂,工厂方法,抽象工厂。工厂模式的重点在于:何时使用工厂模式。
简单工厂
What
举个例子, 在下面这段代码中,根据配置文件的后缀(json、xml、yaml、properties)选择不同的解析器,将存储在文件中的配置解析成内存对象RuleConfig。
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = null; if ("json".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new JsonRuleConfigParser(); } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new XmlRuleConfigParser(); } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new YamlRuleConfigParser(); } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new PropertiesRuleConfigParser(); } else { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = ""; //从ruleConfigFilePath文件中读取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名获取扩展名,比如rule.json,返回json return "json"; } }
根据重构和规范(前文)的内容,可以将功能相对独立的代码封装以提升可读性。将parser部分的代码剥离:
public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = createParser(ruleConfigFileExtension); if (parser == null) { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = ""; //从ruleConfigFilePath文件中读取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名获取扩展名,比如rule.json,返回json return "json"; } private IRuleConfigParser createParser(String configFormat) { IRuleConfigParser parser = null; if ("json".equalsIgnoreCase(configFormat)) { parser = new JsonRuleConfigParser(); } else if ("xml".equalsIgnoreCase(configFormat)) { parser = new XmlRuleConfigParser(); } else if ("yaml".equalsIgnoreCase(configFormat)) { parser = new YamlRuleConfigParser(); } else if ("properties".equalsIgnoreCase(configFormat)) { parser = new PropertiesRuleConfigParser(); } return parser; } }
为了让职责更加单一,我们还可以将createParser函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是简单工厂模式类。
How
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension); if (parser == null) { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = ""; //从ruleConfigFilePath文件中读取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名获取扩展名,比如rule.json,返回json return "json"; } } public class RuleConfigParserFactory { public static IRuleConfigParser createParser(String configFormat) { IRuleConfigParser parser = null; if ("json".equalsIgnoreCase(configFormat)) { parser = new JsonRuleConfigParser(); } else if ("xml".equalsIgnoreCase(configFormat)) { parser = new XmlRuleConfigParser(); } else if ("yaml".equalsIgnoreCase(configFormat)) { parser = new YamlRuleConfigParser(); } else if ("properties".equalsIgnoreCase(configFormat)) { parser = new PropertiesRuleConfigParser(); } return parser; } }
大部分工厂类都是以factory结尾的,但也不必须。比如Java中的Dateformat,Calendar。除此之外,工厂类中创建对象的方法一般以create开头,比如上文createParser,但是也有getInstance,newInstance,甚至valueOf。
在上文中,Parser每次调用createParser都会创建一个新的实例,可以优化下,如果创建了就存放在缓存中,直接使用。
有点类似单例和工厂模式的结合。以下是第二种工厂模式的code。
public class RuleConfigParserFactory { private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>(); static { cachedParsers.put("json", new JsonRuleConfigParser()); cachedParsers.put("xml", new XmlRuleConfigParser()); cachedParsers.put("yaml", new YamlRuleConfigParser()); cachedParsers.put("properties", new PropertiesRuleConfigParser()); } public static IRuleConfigParser createParser(String configFormat) { if (configFormat == null || configFormat.isEmpty()) { return null;//返回null还是IllegalArgumentException全凭你自己说了算 } IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase()); return parser; } }
开闭原则:如果不是频繁添加Parser,也不是特别违背OCP。
多态:用多态来代替多个if,如果if不是很多,也还好。为了提升拓展性而使用多态,造成多个类会影响可读性。
以上这些就是权衡。要看实际情况是否会频繁修改Parser,或者Parser很多。
When or Why
单一职责创建对象。将较为复杂的对象创建逻辑单独封装。
工厂方法
如果非要去掉if分支逻辑,那可以用多态。
public interface IRuleConfigParserFactory { IRuleConfigParser createParser(); } public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new JsonRuleConfigParser(); } } public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new XmlRuleConfigParser(); } } public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new YamlRuleConfigParser(); } } public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new PropertiesRuleConfigParser(); } }
这个就是工厂方法的典型实现。比起简单工厂模式,他更符合开闭原则。
但是, 看一下实际使用吧:(是不是感觉又回到起点了😂)
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParserFactory parserFactory = null; if ("json".equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new JsonRuleConfigParserFactory(); } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new XmlRuleConfigParserFactory(); } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new YamlRuleConfigParserFactory(); } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new PropertiesRuleConfigParserFactory(); } else { throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath); } IRuleConfigParser parser = parserFactory.createParser(); String configText = ""; //从ruleConfigFilePath文件中读取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名获取扩展名,比如rule.json,返回json return "json"; } }
我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension); if (parserFactory == null) { throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath); } IRuleConfigParser parser = parserFactory.createParser(); String configText = ""; //从ruleConfigFilePath文件中读取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名获取扩展名,比如rule.json,返回json return "json"; } } //因为工厂类只包含方法,不包含成员变量,完全可以复用, //不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。 public class RuleConfigParserFactoryMap { //工厂的工厂 private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>(); static { cachedFactories.put("json", new JsonRuleConfigParserFactory()); cachedFactories.put("xml", new XmlRuleConfigParserFactory()); cachedFactories.put("yaml", new YamlRuleConfigParserFactory()); cachedFactories.put("properties", new PropertiesRuleConfigParserFactory()); } public static IRuleConfigParserFactory getParserFactory(String type) { if (type == null || type.isEmpty()) { return null; } IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase()); return parserFactory; } }
当我们添加新的Parser的时候,我们不必像简单工厂模式一样违反开闭原则,直接修改createParser方法增加if 分支,而是利用多态,新增一个新的实现然后添加到cachedFactories即可。
但实际上,对于Parser这个case,工厂方法会增加很多新类但是每个类本身的实现不复杂,只是一个new操作。没必要设计成独立的类,这个场景下简单工厂更合适一点。
当对象的创建逻辑比较复杂,不是简单的new一下,而是要组合其他类对象,并且做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过度复杂。而使用简单工厂模式,将所有的创建逻辑都放在一个工厂类中,会导致这个工厂类变复杂。
抽象工厂
在简单工厂和工厂方法中,类只有一种分类方式(按文件格式)。但是如果类有两种分类:
针对规则配置的解析器:基于接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser
针对系统配置的解析器:基于接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser
我们就要写八个工厂类,如果未来增加了其他分类,又要再加四个。
抽象工厂是应对这个情况,让一个工厂负责创建多个不同类型的对象。而不是只创建一种Parser。
public interface IConfigParserFactory { IRuleConfigParser createRuleParser(); ISystemConfigParser createSystemParser(); //此处可以扩展新的parser类型,比如IBizConfigParser } public class JsonConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new JsonRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new JsonSystemConfigParser(); } } public class XmlConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new XmlRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new XmlSystemConfigParser(); } } // 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码
重点回顾
何时使用工厂模式
封装对象的创建过程,将对象的创建和使用分离。
- 类似规则配置解析的例子,存在if-else动态的根据不同的类型创建不同的对象。
- 对象本身创建过程比较复杂,组合、初始化操作。
对于创建逻辑比较简单的时候可以用简单工厂模式,将多个对象创建逻辑放到一个类中。反之,为了避免一个工厂类过于庞大,我们可以拆分更细,使用工厂方法模式。
- 封装变化:创建逻辑可能变化,封装后对调用者更透明。
- 代码复用:工厂类。
- 隔离复杂:调用者不需要了解如何创建。
- 控制复杂度:将创建的逻辑抽离,让原本的函数或类职责更单一。
工厂模式是对创建方法的封装和抽象,创建的复杂度无法被抵消,只能被转移到工厂内部消化。
实例:如何设计实现一个依赖注入(Dependency Injection)框架
工厂模式和DI容器有什么区别?
- DI容器低层的思想就是基于工厂模式。它相当于一个大的工厂类,负责在程序启动的时候根据配置事先创建好对象。当程序需要哪个类对象的时候直接从容器中拿。
- DI容器相对于之前的例子,他操作的是整个工程的对象。
- DI容器除了负责创建,还要负责配置解析、对象声明周期管理。
DI容器的核心功能有哪些?
如上文所说:配置解析、对象创建和对象生命周期管理。
- 配置解析。在工厂模式中,工厂要创建哪个类都是事先写死的。但是对于框架来说,是和应用高度解耦的。要告诉框架创建什么对象,就需要配置文件提供信息。
举个例子:
public class RateLimiter { private RedisCounter redisCounter; public RateLimiter(RedisCounter redisCounter) { this.redisCounter = redisCounter; } public void test() { System.out.println("Hello World!"); } //... } public class RedisCounter { private String ipAddress; private int port; public RedisCounter(String ipAddress, int port) { this.ipAddress = ipAddress; this.port = port; } //... }
配置文件beans.xml:
<beans> <bean id="rateLimiter" class="com.xzg.RateLimiter"> <constructor-arg ref="redisCounter"/> </bean> <bean id="redisCounter" class="com.xzg.redisCounter"> <constructor-arg type="String" value="127.0.0.1"> <constructor-arg type="int" value=1234> </bean> </beans>
-
对象创建
对于容器来说,不会给每个类都创建一个工厂类(太复杂,过于繁琐),我们给整个容器一个工厂类即可,管理所有bean。
另外,借助反射, 这个大的工厂类不会随着bean的增加而代码量线性膨胀。反射可以实现在程序运行过程中动态加载类、创建对象。不需要事先写死要创建哪些对象。
-
对象的生命周期管理。
简单工厂的视线有两种,每次都返回一个新对象,和返回单例对象。在Spring框架中,可以通过配置scope属性,来区分他们。scope=prototype表示返回新创建的对象,scope=singleton表示返回单例对象。
也可以配置懒加载。
对象的init-method和destroy-method方法也可以配置。这两个方法是在对象创建和销毁的时候可以增加额外操作(赋值、清理)。
如何实现一个DI容器
核心逻辑: 配置文件解析和根据配置文件来利用反射创建对象。
1. 最小原型设计
配置文件beans.xml <beans> <bean id="rateLimiter" class="com.xzg.RateLimiter"> <constructor-arg ref="redisCounter"/> </bean> <bean id="redisCounter" class="com.xzg.redisCounter" scope="singleton" lazy-init="true"> <constructor-arg type="String" value="127.0.0.1"> <constructor-arg type="int" value=1234> </bean> </bean
使用
public class Demo { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "beans.xml"); RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter"); rateLimiter.test(); //... } }
2.提供执行入口
ApplicationContext是接口,ClassPathXmlApplicationContext是实现类。
public interface ApplicationContext { Object getBean(String beanId); } public class ClassPathXmlApplicationContext implements ApplicationContext { private BeansFactory beansFactory; private BeanConfigParser beanConfigParser; public ClassPathXmlApplicationContext(String configLocation) { this.beansFactory = new BeansFactory(); this.beanConfigParser = new XmlBeanConfigParser(); loadBeanDefinitions(configLocation); } private void loadBeanDefinitions(String configLocation) { InputStream in = null; try { in = this.getClass().getResourceAsStream("/" + configLocation); if (in == null) { throw new RuntimeException("Can not find config file: " + configLocation); } List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in); beansFactory.addBeanDefinitions(beanDefinitions); } finally { if (in != null) { try { in.close(); } catch (IOException e) { // TODO: log error } } } } @Override public Object getBean(String beanId) { return beansFactory.getBean(beanId); } }
3. 配置文件解析
较为复杂。BeanConfigParser接口和XmlBeanConfigParser实现类,负责将配置文件解析为BeanDefinition结构,以便BeanFactory依据这个结构创建对象。
代码思路:
public interface BeanConfigParser { List<BeanDefinition> parse(InputStream inputStream); List<BeanDefinition> parse(String configContent); } public class XmlBeanConfigParser implements BeanConfigParser { @Override public List<BeanDefinition> parse(InputStream inputStream) { String content = null; // TODO:... return parse(content); } @Override public List<BeanDefinition> parse(String configContent) { List<BeanDefinition> beanDefinitions = new ArrayList<>(); // TODO:... return beanDefinitions; } } public class BeanDefinition { private String id; private String className; private List<ConstructorArg> constructorArgs = new ArrayList<>(); private Scope scope = Scope.SINGLETON; private boolean lazyInit = false; // 省略必要的getter/setter/constructors public boolean isSingleton() { return scope.equals(Scope.SINGLETON); } public static enum Scope { SINGLETON, PROTOTYPE } public static class ConstructorArg { private boolean isRef; private Class type; private Object arg; // 省略必要的getter/setter/constructors } }
4. 核心工厂类设计
BeanFactory 负责根据BeanDefinition创建bean。
如果对象的scope属性singleton,他会在第一次创建后放进一个map缓存以供后续使用,如果是prototype,他会每次在被调用的时候都创建一个新对象。
写死的代码可以Java编译运行的时候创建对象,动态加载类和创建对象可以利用反射自己编写。
public class BeansFactory { private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>(); public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) { for (BeanDefinition beanDefinition : beanDefinitionList) { this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition); } for (BeanDefinition beanDefinition : beanDefinitionList) { if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) { createBean(beanDefinition); } } } public Object getBean(String beanId) { BeanDefinition beanDefinition = beanDefinitions.get(beanId); if (beanDefinition == null) { throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId); } return createBean(beanDefinition); } @VisibleForTesting protected Object createBean(BeanDefinition beanDefinition) { if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) { return singletonObjects.get(beanDefinition.getId()); } Object bean = null; try { Class beanClass = Class.forName(beanDefinition.getClassName()); List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs(); if (args.isEmpty()) { bean = beanClass.newInstance(); } else { Class[] argClasses = new Class[args.size()]; Object[] argObjects = new Object[args.size()]; for (int i = 0; i < args.size(); ++i) { BeanDefinition.ConstructorArg arg = args.get(i); if (!arg.getIsRef()) { argClasses[i] = arg.getType(); argObjects[i] = arg.getArg(); } else { BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg()); if (refBeanDefinition == null) { throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg()); } argClasses[i] = Class.forName(refBeanDefinition.getClassName()); argObjects[i] = createBean(refBeanDefinition); } } bean = beanClass.getConstructor(argClasses).newInstance(argObjects); } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new BeanCreationFailureException("", e); } if (bean != null && beanDefinition.isSingleton()) { singletonObjects.putIfAbsent(beanDefinition.getId(), bean); return singletonObjects.get(beanDefinition.getId()); } return bean; } }
createBean是一个递归函数,如果我们错误的配置了对象之间的依赖导致循环依赖,createBean会出现栈溢出。如何解决?
个人一个见解就是在解析的过程中,增加一个校验逻辑,如果A依赖B,则去检查B的依赖中是否有A,如有则直接报错。
//TODO: ADD a solution on how Spring solve this.