目录
工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
工厂方法将创建对象的过程与依赖于对象接口的代码分离。例如,应用程序需要具有特定接口的对象来执行其任务。接口的具体实现由某些参数标识。该应用程序没有使用复杂的if/elif/else
条件结构来确定具体的实现,而是将该决定委托给创建具体对象的单独组件。使用这种方法,可以简化应用程序代码,使其更可重用且更易于维护。
1.工厂方法
#1 一个示例
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,这里有一个应用程序,功能是将歌曲的信息序列化为JSON、XML的字符串格式。
'''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':
'''序列化为JSON格式
'''
song_info = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(song_info)
elif format == 'XML':
'''序列化为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:
'''format参数为其他,抛出异常
'''
raise ValueError(format)
在上面的示例中,您有一个基本的Song类来表示一首歌,还有一个songsserializer类可以根据format参数的值将Song对象转换为其字符串表示。serealize()方法支持JSON、XML两种格式。
用上面的应用程序对歌曲梁博的黑夜中进行序列化,id随便选个2019:
'''run.py
'''
import serializer_demo as sd
song = sd.Song('2019', 'Hei Ye Zhong', 'Liang Bo')
serializer = sd.SongSerializer()
j=serializer.serialize(song, 'JSON')
print(j)
x=serializer.serialize(song, 'XML')
print(x)
y=serializer.serialize(song, 'YAML')
print(y)
执行结果:
{"id": "2019", "title": "Hei Ye Zhong", "artist": "Liang Bo"}
<song id="2019"><title>Hei Ye Zhong</title><artist>Liang Bo</artist></song>
Traceback (most recent call last):
File "F:\workspace\PDS_progs\sort\run.py", line 14, in <module>
y=serializer.serialize(song, 'YAML')
File "F:\workspace\PDS_progs\sort\serializer_demo.py", line 37, in serialize
raise ValueError(format)
ValueError: YAML
Song类的format参数为'JSON'和'XML'的都序列化成功了,'YAML'序列化失败了,这在意料之中,因为serealize()方法只支持JSON、XML两种格式。
#2.发现问题
在上面的示例中,根据format
参数的值,通过使用if/elif/else
结构,使共有三个逻辑或执行路径,这看起来似乎没什么大不了的,但实际上它很难维护,因为它做得太多。按照单一职责原则规定,一个模块,一个类,甚至是方法应该有一个单一的,定义明确的责任。它应该只做一件事,并且只有一个改变的理由。
假设遇到以下情况,需要对SongSerializer类中的serialize(self, song, format)方法进行更改:
-- 引入新格式,例如'YAML',那么就需要对方法进行更改,以实现对新格式的序列化功能
-- Song的属性有增加或删除,那么需要对方法进行更改,以适应新的格式
......
#3 将代码重构为所需的接口
示例中所需的接口是个对象或函数,接受Song实例对象,返回字符串表示。现在把代码重构到接口上,两个接口命名为_serialize_to_json()、_serialize_to_xml():
'''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':
return self._serialize_to_json(song)
elif format == 'XML':
return self._serialize_to_xml(song)
else:
raise ValueError(format)
'''接口,接受song
'''
def _serialize_to_json(self,song):
song_info = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(song_info)
'''接口,接受song
'''
def _serialize_to_xml(self,song):
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')
执行成功,和原来的执行结果相同。但现在的程序代码更清爽,易于阅读、理解。
#4 工厂方法的基本实现
工厂方法的中心思想是提供一个单独的组件,该组件负责根据某些指定的参数来决定应使用哪种具体实现。在我们的示例中,该参数为format
。现在新增一个_get_serializer(format),作为单独组件,判断format参数来选择接口。
'''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):
serializer = _get_serializer(format)
return serializer(song)
'''单独组件,处理format
'''
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):
song_info = {
'id': song.song_id,
'title': song.title,
'artist': song.artist
}
return json.dumps(song_info)
def _serialize_to_xml(song):
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')
serialize() 是依赖于接口来完成其任务的应用程序代码。
_get_serializer() 具体工厂(Concrete Creator)角色,实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。
_serialize_to_json()和_serialize_to_xml() 具体产品(Concrete Product)角色,实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。实际情况中,工厂、产品不是应用程序类的方法。
2. 认识使用工厂方法的机会
在应用程序(客户端)依赖于接口(产品)执行任务并且该接口有多个具体实现的每种情况下,都应使用工厂方法。您需要提供一个可以识别具体实现的参数,并在创建者中使用它来确定具体实现。符合此描述的问题很多,因此让我们看一些具体示例。
#1 替换复杂的逻辑代码:格式if/elif/else
难以维护复杂的逻辑结构,因为随着需求的变化需要新的逻辑路径。
Factory Method是一个很好的替代方法,因为您可以将每个逻辑路径的主体放入具有公共接口的单独的函数或类中,并且创建者可以提供具体的实现。条件中评估的参数将成为识别具体实现的参数。上面的示例代表了这种情况。
#2 从外部数据构造相关对象:想象一个需要从数据库或其他外部源检索员工信息的应用程序。
记录代表具有不同角色或类型的员工:经理,办公室文员,销售助理等。应用程序可以在记录中存储代表员工类型的标识符,然后使用Factory Method Employee
从记录中的其余信息创建每个具体对象。
#3 支持相同功能的多种实现:图像处理应用程序需要将卫星图像从一个坐标系转换到另一个坐标系,但是有多种算法具有不同的精度级别来执行转换。
该应用程序可以允许用户选择一个标识具体算法的选项。Factory Method可以基于此选项提供算法的具体实现。
#4 在公共界面下结合相似的功能:在图像处理示例之后,应用程序需要对图像应用滤镜。可以通过一些用户输入来标识要使用的特定过滤器,并且Factory Method可以提供具体的过滤器实现。
#5 集成相关的外部服务:音乐播放器应用程序希望与多个外部服务集成,并允许用户选择其音乐的来源。该应用程序可以为音乐服务定义一个通用接口,并使用“工厂方法”基于用户首选项创建正确的集成。
以上所有情况都是相似的。它们都定义了一个客户端,该客户端依赖于称为产品的通用接口。它们都提供了一种方法来标识产品的具体实现,因此它们都可以在设计中使用工厂方法。
3. 通用对象工厂
4. 专业化对象工厂以提高代码可读性