美文网首页
02、工厂模式

02、工厂模式

作者: vannesspeng | 来源:发表于2020-05-19 08:51 被阅读0次

    简单工厂模式(静态工厂)

    举例:我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。

    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;
      }
    }
    

    以上一种实现方法叫作简单工厂模式的第一种实现方法,把下面这种实现方法叫作简单工厂模式的第二种实现方法。

    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,那势必要改动到
    RuleConfigParserFactory 的代码,那这是不是违反开闭原则呢?实际上,如果不是需要频繁地添加新的 parser,只是偶尔修改一下 RuleConfigParserFactory 代码,稍微不符合开闭原则也是完全可以接受的。

    工厂方法模式

    如果我们非得要将 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 IRuleConfigParserFact
      @Override
      public IRuleConfigParser createParser() {
        return new PropertiesRuleConfigParser();
    

    实际上,这就是工厂方法模式的典型代码实现。这样当我们新增一种 parser 的时候,只需要新增一个实现了 IRuleConfigParserFactory 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

    从上面的工厂方法的实现来看,一切都很完美,但是实际上存在挺大的问题。问题存在于这些工厂类的使用上。接下来,我们看一下,如何用这些工厂类来实现 RuleConfigSource的 load() 函数。具体的代码如下所示:

    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 support");
      }
    
      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";
      }
    }
    

    从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。那怎么来解决这个问题呢?

    我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

    RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。

    image

    当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parserfactory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。

    实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工方法厂模式更加合适。

    抽象工厂模式

    抽象工厂模式也就是不仅生产鼠标,同时生产键盘。

    也就是PC厂商是个父类,有生产鼠标,生产键盘两个接口。

    戴尔工厂,惠普工厂继承它,可以分别生产戴尔鼠标+戴尔键盘,和惠普鼠标+惠普键盘。

    创建工厂时,由戴尔工厂创建。

    后续工厂.生产鼠标()则生产戴尔鼠标,工厂.生产键盘()则生产戴尔键盘。

    image

    在抽象工厂模式中,假设我们需要增加一个工厂

    假设我们增加华硕工厂,则我们需要增加华硕工厂,和戴尔工厂一样,继承PC厂商。

    之后创建华硕鼠标,继承鼠标类。创建华硕键盘,继承键盘类。

    即可。

    image

    在抽象工厂模式中,假设我们需要增加一个产品

    假设我们增加耳麦这个产品,则首先我们需要增加耳麦这个父类,再加上戴尔耳麦,惠普耳麦这两个子类。

    之后在PC厂商这个父类中,增加生产耳麦的接口。最后在戴尔工厂,惠普工厂这两个类中,分别实现生产戴尔耳麦,惠普耳麦的功能。

    以上。

    image

    总结

    当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创 建过程,将对象的创建和使用相分离。何为创建逻辑比较复杂呢?我总结了下面两种情况。

    第一种情况:类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else 创建对象的代码抽离出来,放到工厂类中。

    第二种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身 的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中

    对于第一种情况,当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将 多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避 免设计一个过于庞大的简单工厂类,我推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创 建逻辑就比较复杂,所以,我建议使用工厂方法模式。

    除了刚刚提到的这几种情况之外,如果创建对象的逻辑并不复杂,那我们就直接通过 new 来创建对象就可以了,不需要使用工厂模式。

    相关文章

      网友评论

          本文标题:02、工厂模式

          本文链接:https://www.haomeiwen.com/subject/ksgbnhtx.html