编译自https://realpython.com/factory-method-python/ 源代码有一定的修改
工厂模式不需要详细解释了,具体的可以在 Design Patterns: Elements of Reusable Object-Oriented Software 书中找到。直接进入正题吧。
假设现在需要序列化一条数据,这条数据是一首歌,它包含了歌的名称、创作者和id。序列化的方式暂时有json、xml两种,因此你第一时间想到的代码可能像下面一样。
# In serializer_demo.py
import json
import xml.etree.ElementTree as et
from dataclasses import dataclass
@dataclass
class Song:
song_id: str
title: str
artist: str
class SongSerializer:
def serialize(self, song, format):
if format == 'JSON':
song_info = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(song_info)
elif format == 'XML':
song_info = et.Element('song', attrib={'id': song.song_id})
title = et.SubElement(song_info, 'title')
title.text = song.title
artist = et.SubElement(song_info, 'artist')
artist.text = song.artist
return et.tostring(song_info, encoding='unicode')
else:
raise ValueError(format)
上面的代码包含了一个Song
类以及将Song
序列化的类,通过运行serialize
可以将Song
类序列化成相应格式的字符串,例如:
>>> import serializer_demo as sd
>>> song = sd.Song('1', 'Water of Love', 'Dire Straits')
>>> serializer = sd.SongSerializer()
>>> serializer.serialize(song, 'JSON')
'{"id": "1", "title": "Water of Love", "artist": "Dire Straits"}'
>>> serializer.serialize(song, 'XML')
'<song id="1"><title>Water of Love</title><artist>Dire Straits</artist></song>'
>>> serializer.serialize(song, 'YAML')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "./serializer_demo.py", line 30, in serialize
raise ValueError(format)
ValueError: YAML
如果像序列化其他格式,例如输入YAML时会报错,抛出异常表示不支持这种序列化。
显然如果是紧急需求,或者是没有经过思考的话,上面的代码的确是一挥而就,完成任务。作为一名不断精进的工程师,肯定是不会满足于上述代码,那么上述代码存在什么问题需要去改进呢?
serialize
方法需要进行修改以适应这种变化。Song
这个对象发生改变时,也需要重新修改SongSerializer
这个类以适应Song结构的变化。serialize
方法也必须更改,因为这些东西都是硬编码在代码里的。在重构代码之前,应先思考抽象出其核心逻辑,确定每一个 if/elif/else 的执行路径(或者是逻辑路径)的公共目标。基于上述目标,应该将json、xml的序列化方法抽象出相应的接口替代具体执行方法,再具体实现每一种序列化格式对应序列化方法,最后抽象 if/elif/else 的运行逻辑。
class SongSerializer:
def serialize(self, song, format):
if format == 'JSON':
return self._serialize_to_json(song)
elif format == 'XML':
return self._serialize_to_xml(song)
else:
raise ValueError(format)
def _serialize_to_json(self, song):
payload = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(payload)
def _serialize_to_xml(self, song):
song_element = et.Element('song', attrib={'id': song.song_id})
title = et.SubElement(song_element, 'title')
title.text = song.title
artist = et.SubElement(song_element, 'artist')
artist.text = song.artist
return et.tostring(song_element, encoding='unicode')
上述代码中把 if/elif/else 对应的逻辑路径下的序列化方法抽象出了 _serialize_to_json
和 _serialize_to_xml
方法。这一版的代码相对于之前更易读和容易理解了,但是之前提到的问题依然没有得到解决,需要进一步重构。
上一版代码只是把具体的序列化方法进行了重构,这一版代码把 if/elif/else 抽象成 _get_serializer
方法,从而将选择执行相应的逻辑路径的方法也抽离出来。
class SongSerializer:
def serialize(self, song, format):
serializer = self._get_serializer(format)
return serializer(song)
def _get_serializer(self, format):
if format == 'JSON':
return self._serialize_to_json
elif format == 'XML':
return self._serialize_to_xml
else:
raise ValueError(format)
def _serialize_to_json(self, song):
payload = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(payload)
def _serialize_to_xml(self, song):
song_element = et.Element('song', attrib={'id': song.song_id})
title = et.SubElement(song_element, 'title')
title.text = song.title
artist = et.SubElement(song_element, 'artist')
artist.text = song.artist
return et.tostring(song_element, encoding='unicode')
现在 serialize 方法内部所有逻辑都被抽象成相应的接口, if/elif/else 逻辑被放在了 _get_serializer
的内部方法里,也就是工厂模式里的creator(选择要使用哪个序列化方法)。具体的序列化方法 _serialize_to_json
和 _serialize_to_xml
就是product(具体执行序列化的逻辑)。SongSerializer
则是所谓的client。
为了使得_serialize_to_xml
和_serialize_to_json
可以更灵活的添加和实现,而不需要修改原始的SongSerializer
类变成彻底的client,可以将相应的工厂方法外提成同一个文件下的外部函数。
class SongSerializer:
def serialize(self, song, format):
serializer = get_serializer(format)
return serializer(song)
def get_serializer(format):
if format == 'JSON':
return _serialize_to_json
elif format == 'XML':
return _serialize_to_xml
else:
raise ValueError(format)
def _serialize_to_json(song):
payload = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(payload)
def _serialize_to_xml(song):
song_element = et.Element('song', attrib={'id': song.song_id})
title = et.SubElement(song_element, 'title')
title.text = song.title
artist = et.SubElement(song_element, 'artist')
artist.text = song.artist
return et.tostring(song_element, encoding='unicode')
这一版代码基本上完成了简单的工厂模式重构,SongSerializer.serialize
作为一个client决定着接口的具体执行,根据相应的标识符调用creator组件get_serializer
中实现序列化。creator根据client传入的参数返回相应的对象,选择对应的product(_serialize_to_json
和 _serialize_to_xml
)完成相应的序列化。
经过相当大的重构,这一版的代码依然还不够尽善尽美,因为还没考虑到如下的情形:
get_serializer
方法。Song
对象可以拥有json和xml的序列化方式,而没有yaml地序列化方式。首先将_serialize_to_xml
和_serialize_to_json
从方法变成对应的抽象接口Serializer
,以方便扩展序列化支持的数据。
# In serializers.py
from abc import ABCMeta, abstractmethod
import json
import xml.etree.ElementTree as et
class Serializer(metaclass=ABCMeta):
@abstractmethod
def start_object(self, object_name, object_id):
pass
@abstractmethod
def add_property(self, name, value):
pass
@abstractmethod
def to_str(self):
pass
class JsonSerializer(Serializer):
def __init__(self):
self._current_object = None
def start_object(self, object_name, object_id):
self._current_object = {
'id': object_id
}
def add_property(self, name, value):
self._current_object[name] = value
def to_str(self):
return json.dumps(self._current_object)
class XmlSerializer(Serializer):
def __init__(self):
self._element = None
def start_object(self, object_name, object_id):
self._element = et.Element(object_name, attrib={'id': object_id})
def add_property(self, name, value):
prop = et.SubElement(self._element, name)
prop.text = value
def to_str(self):
return et.tostring(self._element, encoding='unicode')
Serializer
接口将上述的json和xml序列化方法抽象成了三个方法:
start_object(object_name, object_id)
add_property(name, value)
to_str()
XmlSerializer
和JsonSerializer
都是Serializer
接口的具体实现。
于是具体的Serializer
的执行,也就是client,就可以抽象成下述模式:
# In serializers.py
class ObjectSerializer:
def serialize(self, serializable, format):
serializer = factory.get_serializer(format)
serializable.serialize(serializer)
return serializer.to_str()
这里的ObjectSerializer
的实现便通用许多,只需要serializable
和format
参数。format
用于确定Serializer
的具体执行方法,serializable
可以按照自己本身确定的的serialize
方式实现serialize
方法。
于是将Song
类就重构成serializable
的具体实现:
# In songs.py
class Song:
def __init__(self, song_id, title, artist):
self.song_id = song_id
self.title = title
self.artist = artist
def serialize(self, serializer):
serializer.start_object('song', self.song_id)
serializer.add_property('title', self.title)
serializer.add_property('artist', self.artist)
Song
类实现了Serializable
的具体接口serialize
,在这个接口里Song
类使用serializer
对象写入自己的信息而无需关心具体序列格式,使得Song
类灵活的转换成不同的序列化格式。
到目前为止,这一版代码的client(ObjectSerializer
)和product(serializer
)有了。作为creator的factory
该如何实现。
最简单的factory
方法可以依旧使用 if/else/elif 的逻辑处理数据。
# In serializers.py
class SerializerFactory:
def get_serializer(self, format):
if format == 'JSON':
return JsonSerializer()
elif format == 'XML':
return XmlSerializer()
else:
raise ValueError(format)
factory = SerializerFactory()
经过前面的讨论,if/else/elif 使得上述代码依旧不够灵活,例如要添加yaml的序列化格式就需要修改SerializerFactory
类的源码。因此可以提供类似于注册的模式,也就是register_format
方法,将各个格式注册进SerializerFactory
类,从而灵活的添加对应的序列化方式。
# In serializers.py
class SerializerFactory:
def __init__(self):
self._creators = {}
def register_format(self, format, creator):
self._creators[format] = creator
def get_serializer(self, format):
creator = self._creators.get(format)
if not creator:
raise ValueError(format)
return creator()
factory = SerializerFactory()
factory.register_format('JSON', JsonSerializer)
factory.register_format('XML', XmlSerializer)
yaml格式的序列化可以单独写成一个文件。
# In yaml_serializer.py
import yaml
import serializers
class YamlSerializer(serializers.JsonSerializer):
def to_str(self):
return yaml.dump(self._current_object)
serializers.factory.register_format('YAML', YamlSerializer)
现在执行结果依旧和之前的代码一样:
>>> import serializers
>>> import songs
>>> import yaml_serializer
>>> song = songs.Song('1', 'Water of Love', 'Dire Straits')
>>> serializer = serializers.ObjectSerializer()
>>> print(serializer.serialize(song, 'JSON'))
{"id": "1", "title": "Water of Love", "artist": "Dire Straits"}
>>> print(serializer.serialize(song, 'XML'))
<song id="1"><title>Water of Love</title><artist>Dire Straits</artist></song>
>>> print(serializer.serialize(song, 'YAML'))
{artist: Dire Straits, id: '1', title: Water of Love}
最后一版代码解决了 if/elif/else 模式需要硬编码和不易进行修改的问题,使得代码更易复用和灵活,解耦了client、creator和product的逻辑,避免了增加新的序列化格式就需要修改源代码的问题。
那么你的代码属于哪一层级?