Factory 工厂模式

设计模式 2020-04-07 7092 字 926 浏览 点赞

起步

工厂模式属于创建型,一般分三类:简单工厂、工厂方法、抽象工厂。

大多数工厂类以 Factory 结尾,创建对象的方法一般以 create 开头,但都不强行要求。

简单工厂

现在我们为项目添加一个解析配置文件的功能,而该功能支持解析 json 文件、xml 文件,以及 yaml 文件。因而我们对应有这几个解析器:

class JsonConfigParser(object):
    def parse(self, file):
        return ...

class XmlConfigParser(object):
    def parse(self, file):
        return ...

class YamlConfigParser(object):
    def parse(self, file):
        return ...

然后是提供配置信息的 ConfigSource 类。这个类需要借助解析器解析配置文件,将解析后的数据存放到自己的属性当中,以此提供配置信息。最快能想到的实现方式如下:

class ConfigSource(object):
    def load(self, file):
        # 根据文件扩展名选择对应的解析器
        _, ext = os.path.splitext(file)
        if ext == ".json":
            parser = JsonConfigParser()
        elif ext == ".xml":
            parser = XmlConfigParser()
        elif ext == ".yaml":
            parser = YamlConfigParser()
        else:
            raise NotImplementedError

        self.config = parser.parse(file)

从功能上看,上述做法没有问题。但根据单一职责原则,一个类应该只做一件事,在 ConfigSource 中选择解析器与执行解析器是两件事,所以设计上可以进一步优化。一般,我们把创建解析器的任务交给工厂类去做,ConfigSource 只需要调用工厂类创建对象的方法,具体的创建逻辑无需关心。

class ConfigSource(object):
    def load(self, file):
        _, ext = os.path.splitext(file)
        parser = ConfigParserFactory.create_parser(ext)
        self.config = parser.parse(file)

class ConfigParserFactory(object):
    @classmethod
    def create_parser(cls, ext):
        """
            只负责创建 解析器 的工厂
        """
        if ext == ".json":
            parser = JsonConfigParser()
        elif ext == ".xml":
            parser = XmlConfigParser()
        elif ext == ".yaml":
            parser = YamlConfigParser()
        else:
            raise NotImplementedError
        return parser

这样做既保障了单一职责,也提高了代码的可读性。当你需要梳理配置信息来源时,读到 ConfigSource.load 方法,仅关注三行代码。而工厂类到底怎么创建解析器,其实你并不关心。

工厂方法

一般场合简单工厂已经够用,但也有不一般的时候。当创建解析器不再似 parser = JsonConfigParser() 一行代码就可以创建时,仍要强行将所有逻辑塞进一个方法中,代码耦合度会陡增。

譬如要求项目只有开发环境才能使用 json 解析器;测试环境运行 xml 解析器需要添加计时器,记录解析配置文件耗费的时间,输出到日志。根据最新要求,代码大致成了这样:

class ConfigParserFactory(object):
    @classmethod
    def create_parser(cls, ext):
        """
            只负责创建 解析器 的工厂
        """
        enviorment = os.environ.get("ENVIRONMENT", "product")
        if ext == ".json":
            if enviorment == "development":
                parser = JsonConfigParser()
            else:
                raise PermissionError  # 禁止其他环境使用 json parser
        elif ext == ".xml":
            parser = XmlConfigParser()
            if enviorment == "test":
                parser.parse = record_time(parser.parse)  # 添加计时器
        elif ext == ".yaml":
            parser = YamlConfigParser()
        else:
            raise NotImplementedError
        return parser

def record_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        logging.info(f"一共耗费 {end-start} s")
        return res
    return wrapper

其实只加了两个需求,但已经能看到 if ... else 分支臃肿不堪,我们可以用工厂方法来解决这个问题。

工厂方法的核心在于:用简单工厂来创建简单工厂。这样说有点绕,但其实就一点,工厂方法是要解决简单工厂不能解决的问题。在上述场景中,之所以代码变复杂了,是因为创建解析器难度增加,我们应该把这部分难度转移出去。而不同的解析器,创建的条件不同,所以针对每一种解析器要有对应的工厂类。

class InterfaceConfigParserFactory(metaclass=abc.ABCMeta):
    """
        提供接口规范
    """
    @abc.abstractmethod
    def create_parser(self):
        pass

class JsonConfigParserFactory(InterfaceConfigParserFactory):
    def create_parser(self):
        enviorment = os.environ.get("ENVIRONMENT", "product")
        if enviorment == "development":
            return JsonConfigParser()
        raise PermissionError

class XmlConfigParserFactory(InterfaceConfigParserFactory):
    def create_parser(self):
        enviorment = os.environ.get("ENVIRONMENT", "product")
        parser = XmlConfigParser()
        if enviorment == "test":
            parser.parse = record_time(parser.parse)
        return parser

class YamlConfigParserFactory(InterfaceConfigParserFactory):
    def create_parser(self):
        return YamlConfigParser()

有了这些工厂类之后,ConfigParserFactory 中的代码也要有所调整:

class ConfigParserFactory(object):
    @classmethod
    def get_parser(cls, ext):
        """
            获取工厂类创建的解析器
        """
        if ext == ".json":
            factory = JsonConfigParserFactory()
        elif ext == ".xml":
            factory = XmlConfigParserFactory()
        elif ext == ".yaml":
            factory = YamlConfigParserFactory()
        else:
            raise NotImplementedError

        parser = factory.create_parser()
        return parser

代码的可读性又回归正常状态。同时,解析器对象不是在 ConfigParserFactory 中创建,而是被推迟到了 InterfaceConfigParserFactory 子类中创建。

抽象工厂

回顾简单工厂、工厂方法,能够发现一个特点:二者在一个维度上对工厂类分类。无论 json、还是 xml 或者
yaml,都是语法格式上的区别。如果当前的需求要求,区分用户配置和系统默认配置,按照工厂方法模式就得设计 6 个工厂类:

# 针对用户配置
UserJsonParserFactory
UserXmlParserFactory
UserYamlParserFactory

# 针对系统配置
SystemJsonParserFactory
SystemXmlParserFactory
SystemYamlParserFactory

一旦有新的文件格式需要解析,需要的定义的工厂类就会成倍增加,而过多的类会大大增加代码的维护成本。于是有了抽象工厂,简单来说,就是一个工厂负责创建多个不同类型的对象

class InterfaceConfigParserFactory(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create_user_parser(self):
        pass

    @abc.abstractmethod
    def create_system_parser(self):
        pass

class JsonConfigParserFactory(InterfaceConfigParserFactory):
    def create_user_parser(self):
        return UserJsonConfigParser()

    def create_system_parser(self):
        return SystemJsonConfigParser()

class XmlConfigParserFactory(InterfaceConfigParserFactory):
    def create_user_parser(self):
        return UserXmlConfigParser()

    def create_system_parser(self):
        return SystemXmlConfigParser()

...

总结

使用工厂模式的两个场景:

  • 当客户端需要使用 if ... elif ... else ... 创建不同对象时,可将分支判断移到工厂类中。
  • 当客户端创建一个对象需要各种组合操作,或是各种初始化操作,其创建需要的具体逻辑也可以挪到工厂类。

在我的理解中,简单工厂与工厂方法最大的区别是,前者工厂类只有一个动作,创建与返回合并在一个方法中;后者工厂类包含两个动作,一个是创建对象,一个返回对象。也就是说,对于工厂方法来说,是不是简单工厂创建简单工厂并不重要,重要的是创建逻辑与返回逻辑被隔离开。在 《Learning Python Design Patterns Second Edition》中,其工厂方法的示例如下:

class Profile(metaclass=ABCMeta):
    def __init__(self):
        self.sections = []
        self.createProfile()

    @abstractmethod
    def createProfile(self):
        pass

    def getSections(self):
        return self.sections

    def addSections(self, section):
        self.sections.append(section)

class linkedin(Profile):
    def createProfile(self):
        self.addSections(PersonalSection())
        self.addSections(PatentSection())
        self.addSections(PublicationSection())

class facebook(Profile):
    def createProfile(self):
        self.addSections(PersonalSection())
        self.addSections(AlbumSection())

而抽象工厂针对的是更复杂的创建逻辑,旨在创建一类具有相关性的对象,适合组合场景。

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论