0%

工厂方法

工厂方法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
# In serializer_demo.py

import json
import xml.etree.ElementTree as et

class 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 sd

song = 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
# In serializers.py

import json
import xml.etree.ElementTree as et

class 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
# 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)

这样设计以后具体的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
# 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)

有些也是python这类语言特有的,把一个类作为参数放入dict中,然后在get_serializer函数中,再使用他初始化。

到这一步,即把song和序列化解耦了,而且如果后面新增新的格式,新增playlist等对象,都不会改动现有代码。确实很优雅,很漂亮。当然对于我来说,其实还不具备这么强的设计能力,而且很多代码写成面条代码其实也没有什么大碍,因为可能根本没有用户访问。但是还得向高手学习,一个头脑清醒的分析思路,其实比写出好代码更有用。