IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    设计模式之美-课程笔记25-工厂模式

    10k发表于 2023-08-12 00:00:00
    love 0

    一般工厂模式有三种,简单工厂,工厂方法,抽象工厂。工厂模式的重点在于:何时使用工厂模式。

    简单工厂

    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动态的根据不同的类型创建不同的对象。
    • 对象本身创建过程比较复杂,组合、初始化操作。

    对于创建逻辑比较简单的时候可以用简单工厂模式,将多个对象创建逻辑放到一个类中。反之,为了避免一个工厂类过于庞大,我们可以拆分更细,使用工厂方法模式。

    1. 封装变化:创建逻辑可能变化,封装后对调用者更透明。
    2. 代码复用:工厂类。
    3. 隔离复杂:调用者不需要了解如何创建。
    4. 控制复杂度:将创建的逻辑抽离,让原本的函数或类职责更单一。

    工厂模式是对创建方法的封装和抽象,创建的复杂度无法被抵消,只能被转移到工厂内部消化。

    实例:如何设计实现一个依赖注入(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.



沪ICP备19023445号-2号
友情链接