工厂方法Factory Method这个我曾经尝试用过,就是定义一个虚基类,然后定义几个纯虚接口,然后通过if/else来把对应的子类new 出来,然后就开始工作。感觉用处不大,主要是是很多时候其实需求不会增加这么多,我也觉得奇怪,为啥别人的代码都能自我扩展,而我写的东西总是就不能长大呢。这次参考The Factory Method Pattern and Its Implementation in Python 重新学一下这个模式。
工厂方法是创建型设计模式,用来创建通用接口的具体实现。
避免大幅的if/elif/else这样的面条代码。(这里我又不同意见,首先你这个接口得能够抽象的出来,其次为了实现这种模式,额外代码也有成本)
总体想法:当一个调用方依赖一个接口去执行一个任务,根据不同参数,有不同的任务实现。自己开发中其实很少用到,但是很多框架代码是用到的,为了能看懂代码,所以必须学会这个模式。
使用Factory Method的场景,替换复杂逻辑代码(大量if/elif/else这样的逻辑),从外部数据创建创建对象,支持某个feature的不同实现,整合相似特性在相同的接口下,整合外部相关服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import jsonimport xml.etree.ElementTree as etclass Song : def __init__ (self, song_id, title, artist) : self.song_id = song_id self.title = title self.artist = artist 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对象序列化成json或者xml的实现。基本上我每次都是这么写,甚至会把serialize函数作为Song类的成员函数。现在看有确实是初级水平。他们追求的是,增加需求的时候不要改动现有代码,不会带来隐藏的bug,并且代码具有可读性。
作者说这样的代码是难维护的,因为很多情况都要修改serialize代码,如Song类型增加了字段,增加的文件格式,现有输出格式变化。这些都需要修改serialize函数的代码。他认为之所以会这样就是因为,serialize函数做的太多了。根据单一职责原则,一个模块,类,函数,都只有一个职责,应该只做一件事,且只有一个引起他修改的原因(It should do just one thing and have only one reason to change.)。
这点说的太对了,我之前很多都是混在一起的,没考虑这么多,也不知道怎么拆开。大的功能可能知道怎么拆开,但是这用内部类、函数的设计上,确实不知道该怎么解耦合。时间一久,自己都不想碰自己的代码。
使用FactoryMethod首先找到common interface,这个例子中就是serialize(),需要独立的模块去实现不同格式的序列化功能。同时还要一个独立模块根据参数,确定使用哪个序列化模块的具体实现。这样就从我那种朴素幼稚的开发思路,设计一个Song类型,变成了设计三种类型,一个是Song,一个SongSerialize,一个是具体格式序列化的类。
初级实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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这是就是client,get_serializer就是creator,
1 2 3 4 5 6 7 import serializer_demo as sdsong = sd.Song('1' , 'Water of Love' , 'Dire Straits' ) serializer = sd.SongSerializer() serializer.serialize(song, 'JSON' ) serializer.serialize(song, 'XML' )
这里有时不好理解,还是我之前的理解是错的?song这就不是对象,他就是一个struct,没有成员函数。serializer没有成员函数,只不过就是一函数。所以可能以前的老想法,要稍微变变,不是一个类,就要把关于这个东西的所有属性和方法都要放在一起,那样确实就是面向过程思维,没必要用面向对象了。
任意对象序列化
上面的所谓工厂方法,有个问题,就是serializer并不是通用的模块,他和song的内部数据结构紧紧耦合,形式上是两部分,但实际上就是一个函数。一旦song的增减字段,或者新增一种字段不同需要序列化的对象,就很麻烦。
一般的想法是写serializer.store_info(song),但现在仔细想,发现不对啊,这样没有意义啊,serializer还是要完全知道song的内部结构。实际上应该是song.store_info(serializer),由song把自己信息,通过serializer的接口传入,这才是隔离封装。没能从面向过程的思维方式,转换到面向对象的思维方式。
作者给出了通用序列化的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import jsonimport xml.etree.ElementTree as etclass ObjectSerializer : def serialize (self, serializable, format) : serializer = factory.get_serializer(format) serializable.serialize(serializer) return serializer.to_str() class JsonSerializer : 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 : 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' )
1 2 3 4 5 6 7 8 9 10 11 12 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)
这样设计以后具体的serializer,JsonSerializer、XmlSerializer不用关心需要序列化的对象的内部数据,而只是统一的提供了三个接口:
start_object()
add_property()
to_str()
而song也被抽象为serializable,他需要提供抽象接口serialize()接口,将自己的数据传给serializer。song已经不知道具体是把数据写出什么格式了,JsonSerializer、XmlSerializer也不知道写的是那种对象的数据了,两部分解耦了。这时候基本看不到if/else了。
Object Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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)
有些也是python这类语言特有的,把一个类作为参数放入dict中,然后在get_serializer函数中,再使用他初始化。
到这一步,即把song和序列化解耦了,而且如果后面新增新的格式,新增playlist等对象,都不会改动现有代码。确实很优雅,很漂亮。当然对于我来说,其实还不具备这么强的设计能力,而且很多代码写成面条代码其实也没有什么大碍,因为可能根本没有用户访问。但是还得向高手学习,一个头脑清醒的分析思路,其实比写出好代码更有用。