起步
工厂模式属于创建型,一般分三类:简单工厂、工厂方法、抽象工厂。
大多数工厂类以 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())
而抽象工厂针对的是更复杂的创建逻辑,旨在创建一类具有相关性的对象,适合组合场景。
参考
- 感谢极客时间王争老师的《设计模式之美:工厂模式》
- 《Learning Python Design Patterns Second Edition》之《3. The Factory Pattern》
还不快抢沙发