更多优质内容
请关注公众号

面向对象和设计模式(十二)工厂模式-张柏沛IT博客

正文内容

面向对象和设计模式(十二)工厂模式

栏目:其他内容 系列:面向对象与设计模式 发布时间:2023-01-25 15:17 浏览量:1010
本系列文章目录
展开/收起

工厂模式不是一种模式,而是简单工厂模式、工厂方法模式和抽象工厂模式共3种模式。

工厂模式的用途是创建对象。如果创建一个对象需要大量复杂的初始化操作,那么上层调用者可以将创建对象的工作委托给工厂类来实现,这就是工厂模式的使用场景。

下面我们分别看一下 简单工厂模式 和 工厂方法模式,它们的使用场景也不尽相同。



一、简单工厂模式


简单工厂的使用场景是:当创建对象的逻辑不复杂,而且创建什么对象是根据条件或类型判断来决定的时候就可以使用简单工厂创建和返回一个对象给调用者。

有一个这样的场景,程序要实现一个根据配置文件后缀(json、xml、yaml、conf),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将配置文件中的配置项解析成RuleConfig 内存对象。


最直接的实现方式就是这样:

<?php

class RuleConfigSource {
    public function load($ruleConfigFilePath): RuleConfig  {
      $ruleConfigFileExtension = $this->getFileExtension($ruleConfigFilePath);
      $parser = null;
      if ("json" == $ruleConfigFileExtension) {
        $parser = new JsonRuleConfigParser();
      } else if ("xml"==$ruleConfigFileExtension) {
        $parser = new XmlRuleConfigParser();
      } else if ("yaml"==$ruleConfigFileExtension) {
        $parser = new YamlRuleConfigParser();
      } else if ("properties"==$ruleConfigFileExtension) {
        $parser = new PropertiesRuleConfigParser();
      } else {
        throw new InvalidRuleConfigException(
               "Rule config file format is not supported: " . $ruleConfigFilePath);
      }
  
      $configText = "";
      //从$ruleConfigFilePath文件中读取配置文本到configText中
      $ruleConfig = $parser->parse($configText);
      return $ruleConfig;
    }
  
    private function getFileExtension($filePath) {
      //...解析文件名获取扩展名,比如rule.json,返回json
    }
  }


为了让类的职责更加单一、代码更加清晰,我们可以将创建 Parser 对象的逻辑抽到一个静态类中,这个类就是简单工厂类。

<?php

class RuleConfigParserFactory{
    public static function createParser($ruleConfigFileExtension){
        $parser = null;
        if ("json" == $ruleConfigFileExtension) {
          $parser = new JsonRuleConfigParser();
        } else if ("xml"==$ruleConfigFileExtension) {
          $parser = new XmlRuleConfigParser();
        } else if ("yaml"==$ruleConfigFileExtension) {
          $parser = new YamlRuleConfigParser();
        } else if ("properties"==$ruleConfigFileExtension) {
          $parser = new PropertiesRuleConfigParser();
        }
        return $parser;
    }
}

class RuleConfigSource {
    public function load($ruleConfigFilePath): RuleConfig {
      $ruleConfigFileExtension = $this->getFileExtension($ruleConfigFilePath);
      $parser = RuleConfigParserFactory::createParser($ruleConfigFileExtension);
      if($parser == null){
          throw new InvalidRuleConfigException("Rule config file format is not supported: " . $ruleConfigFilePath);
        }
      $configText = "";
      $ruleConfig = $parser->parse($configText);
      return $ruleConfig;
    }
  
    private function getFileExtension($filePath) {
      //...解析文件名获取扩展名,比如rule.json,返回json
    }
}


如果 parser 可以复用,为了节省内存和对象创建的时间,可以将 parser 事先创建好缓存起来放到工厂类的一个以配置文件后缀为key的map中,调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。

如果你嫌简单工厂里的if else太多,过于繁琐,那么可以使用 map 记录不同条件对应的不同对象的类来代替 if else。

<?php

class RuleConfigParserFactory{
    protected static $parserMap = [
        "json" => JsonRuleConfigParser::class,
        "xml" => XmlRuleConfigParser::class,
        "yaml" => YamlRuleConfigParser::class,
        "properties" => PropertiesRuleConfigParser::class,
    ];
    
    public static function createParser($ruleConfigFileExtension){
        $parser = null;
        if(isset(self::$parserMap[$ruleConfigFileExtension])){
            $class = self::$parserMap[$ruleConfigFileExtension];
            $parser = new $class();
        }
        return $parser;
    }
}



二、工厂方法模式


工厂方法模式的使用场景是当创建对象的逻辑比较复杂的时候,将原来的一个工厂类通过多态化为多个工厂类来创建不同类型的对象给上层调用者。

如果 Parser 对象的创建包含复杂的初始化工作(例如设置各种各样的属性或者申请资源等),而且多态下不同种类的Parser对象的初始化工作五花八门各不相同,那么将这些创建逻辑都放到 简单工厂类 里面就不合适了,扩展性会很差。

此时我们应该针对每一种 Parser 对象的创建/初始化操作都设置一个工厂类,这就是工厂方法模式。

interface IRuleConfigParserFactory {
    public static function createParser();
  }
  
  class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
      $parser = new JsonRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
        $parser = new XmlRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
        $parser = new YamlRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
      //...
    }
  }


单纯使用工厂方法模式的话,上传调用者就需要根据类型或条件决定该使用哪个工厂类,相当于迫使调用者写一堆 if else 或 Switch case 或者建一个 map。

我们可以通过为工厂类再创建一个简单工厂,记录文件配置类型 和 工厂方法类的映射关系,在简单工厂中让工厂方法类创建我们真正想要的对象。

<?php

interface IRuleConfigParserFactory {
    public static function createParser();
  }
  
  class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
      $parser = new JsonRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
        $parser = new XmlRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
        $parser = new YamlRuleConfigParser();
      // ...$parser的初始化操作、设置属性等工作
      return $parser;
    }
  }
  
  class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
    public static function createParser() {
      //...
    }
  }

class RuleConfigParserFactoryMap{
    protected static $factoryMap = [
        "json" => JsonPropertiesRuleConfigParserFactory::class,
        "xml" => XmlPropertiesRuleConfigParserFactory::class,
        "yaml" => YamlPropertiesRuleConfigParserFactory::class,
        "properties" => PropertiesPropertiesRuleConfigParserFactory::class,
    ];

    public static function createParser($ruleConfigFileExtension){
        $parser = null;
        if(isset(self::$factoryMap[$ruleConfigFileExtension])){
            $class = self::$factoryMap[$ruleConfigFileExtension];
            $parser = $class::createParser();
        }
        return $parser;
    }
}


工厂模式并非没有代价,额外创建诸多 Factory 类会增加代码的复杂性。之所以使用工厂模式,是为了将创建对象的逻辑从业务逻辑中剥离出来,剥离之后能让代码更加清晰,更加可读、可维护。所以如果对象的创建是一句new就能解决的事情,那么就没有必要使用工厂模式,直接在业务代码中new就好了。


我们上升一个思维层面来看工厂模式,它的作用无外乎下面这四个。这也是判断要不要使用工厂模式的最本质的参考标准。

封装变化:如果创建对象的逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明,且更易扩展。如果是这样的话,就值得用工厂模式。

代码复用:创建代码抽离到独立的工厂类之后可以复用。如果有很多地方都需要用到对象的创建和复杂初始化,那么也值得用工厂模式。

隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。

控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。


最后,关于抽象工厂模式,在实际工作中用的的情况比较少,这里就不再细说,它的使用场景是当创建对象所依据的类型或条件是多维的,例如我不仅要根据文件后缀类型,还要根据文件大小区间来决定实例化哪类对象,此时就可以考虑使用抽象工厂。




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 面向对象和设计模式(十二)工厂模式

热门推荐
推荐新闻