一、什么是设计模式?
软件工程中,设计模式是指软件设计问题的推荐方案。设计模式一般是描述如何组织代码和使用最佳实践来解决常见的设计问题。需谨记一点:设计模式是高层次的方案,并不关注具体的实现细节,比如算法和数据结构。对于正在尝试解决的问题,何种算法和数据结构最优,则是由软件工程自己把握。
二、python实现设计模式
设计模式共分为三类
- 创建型模式
- 结构型模式
- 行为型模式
(1)创建型模式
1.单例模式
单例模式(Singleton Pattern)是一个常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在,当希望在某个系统中只出现一个实例时,单例对象就能派上用场。
class Singleton():
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(id(s1),id(s2))
2. 工厂模式
在工厂模式设计中,客户端可以请求一个对象,而无需知道这个对象来自哪里(使用类来生成对象)。工厂背后的思想是简化对象的创建,基于一个中心化的函数来实现创建对象,更易于追踪创建了哪些对象。通过将创建对象的代码和使用对象的代码解耦,工厂能够降低应用维护的复杂度。
在工厂方法中,通过执行单个函数,传入一个参数(表名想要什么),但是并不要求知道对象是如何实现以及对象来自哪里。
# 基类
class Person():
def __init__(self):
self.name = None
self.gender = None
def getName(self):
return self.name
def getGender(self):
return self.gender
#
class Male(Person):
def __init__(self,name):
# super().__init__()
print(name)
class Female(Person):
def __init__(self,name):
# super().__init__()
print(name)
class Factory():
def getPerson(self,name,gender):
if gender == "M":
return Male(name)
if gender =="F":
return Female(name)
if __name__ == '__main__':
factory = Factory()
person = factory.getPerson("Waltz","M")
3. 建造者模式
将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。在该模式中,有两个参与者:建造者(builder)和指挥者(director)。建造者负责创建复杂对象的各个组成部分。
与工厂模式的不同之处:
1、想要创建一个复杂对象(对象由多个部分构成,且对象的创建要经过多个不同的步骤,这些步骤还需要遵从特定的顺序)。
2、要求一个对象能有不同的表现,并希望将对象的构造与表现解耦。
3、想要在某个时间点创建对象,但在稍后的时间点再访问
示例:
1.有一个接口类,定义创建对象的方法。一个指挥员类,接受创造者对象为参数。两个创造者类,创建对象方法相同,内部创建可自定义
2.一个指挥员,两个创造者(瘦子 胖子),指挥员可以指定由哪个创造者来创造
from abc import ABCMeta, abstractmethod
class Builder():
__metaclass__ = ABCMeta
@abstractmethod
def draw_left_arm(self):
pass
@abstractmethod
def draw_right_arm(self):
pass
@abstractmethod
def draw_left_foot(self):
pass
@abstractmethod
def draw_right_foot(self):
pass
@abstractmethod
def draw_head(self):
pass
@abstractmethod
def draw_body(self):
pass
class Thin(Builder):
def draw_left_arm(self):
print("画左手")
def draw_right_arm(self):
print("画右手")
def draw_left_foot(self):
print("画左脚")
def draw_right_foot(self):
print("画右脚")
def draw_head(self):
print("画头")
def draw_body(self):
print("画瘦身体")
class Fat(Builder):
def draw_left_arm(self):
print("画左手")
def draw_right_arm(self):
print("画右手")
def draw_left_foot(self):
print("画左脚")
def draw_right_foot(self):
print("画右脚")
def draw_head(self):
print("画头")
def draw_body(self):
print("画胖身体")
class Director():
def __init__(self,person):
self.person = person
def draw(self):
self.person.draw_left_arm()
self.person.draw_right_arm()
self.person.draw_left_foot()
self.person.draw_right_foot()
self.person.draw_head()
self.person.draw_body()
if __name__ == '__main__':
thin = Thin()
fat = Fat()
director_thin = Director(thin)
director_thin.draw()
director_fat = Director(fat)
director_fat.draw()
4. 原型模式
原型模式:Prototype design pattern
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
原型模式本质是克隆对象,所以在队形初始化操作比较复杂的情况下,很实用,能大大降低耗时,提高性能。因为“不用重新初始化对象,而是动态获得对象运行的状态”。属于副本的概念,与引用不同。
引用和副本的区别:
引用本质共用仍然是相同的,类似浅拷贝。
副本是各自独有的,类似深拷贝。
引用是共用同一个,副本是在各自的副本进行修改,互不影响。
浅拷贝:指对象的字段被拷贝,而字段引用的对象不回被拷贝,拷贝的对象和源对象只是名称相同,但是他们共用一个实体
深拷贝:对对象实例中字段引用的对象也进行拷贝。
深浅拷贝官方介绍:
浅拷贝构造一个新的复合对象后,(会尽可能地)将在原始对象中找到的对象的引用插入到新对象中。
深拷贝构造一个新的复合对象后,会递归地将原始对象中找到的对象的副本插入新对象中。
原型模式示例:某一本书在初版之后又经历了一次改版,对书名、价钱、页码等进行了修改。但是这次修改并不是重新装订了一本书,只是修改了部分内容,这时候就不需要重新创建一个对象,只需要把原来的对象的副本内容做一些修改。
import copy
from collections import OrderedDict
class Book:
def __init__(self, name, authors, price, **rest):
'''rest的例子有:出版商、长度、标签、出版日期'''
self.name = name
self.authors = authors
self.price = price # 单位为美元
self.__dict__.update(rest)
def __str__(self):
mylist = []
ordered = OrderedDict(sorted(self.__dict__.items()))
for i in ordered.keys():
mylist.append('{}: {}'.format(i, ordered[i]))
if i == 'price':
mylist.append('$')
mylist.append('\n')
return ''.join(mylist)
class Prototype:
def __init__(self):
self.objects = dict()
def register(self, identifier, obj):
self.objects[identifier] = obj
def unregister(self, identifier):
del self.objects[identifier]
def clone(self, identifier, **attr):
found = self.objects.get(identifier)
if not found:
raise ValueError('Incorrect object identifier: {}'.format(identifier))
obj = copy.deepcopy(found)
obj.__dict__.update(attr)
return obj
def main():
b1 = Book('The C Programming Language', ('Brian W. Kernighan', 'Dennis M.Ritchie'),price=118, publisher='Prentice Hall', length=228, publication_date='1978-02-22',tags=('C', 'programming', 'algorithms', 'data structures'))
prototype = Prototype()
cid = 'k&r-first'
prototype.register(cid, b1)
b2 = prototype.clone(cid, name='The C Programming Language(ANSI)', price=48.99,length=274, publication_date='1988-04-01', edition=2)
for i in (b1, b2):
print(i)
print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2)))
if __name__ == '__main__':
main()
(2)结构型模式
1. 适配器模式
适配器模式(Adapter pattern)是一种结构型设计模式,帮助我们实现两个不兼容接口之间
的兼容。首先,解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,
或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但
又并非总是能修改代码,或因为我们无法访问这些代码(例如,组件以外部库的方式提供),或
因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含
让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器。
# 首先是我们有什么?
class Computer:
def __init__(self, name):
self.name = name
def __str__(self):
return 'the {} computer'.format(self.name)
def execute(self):
return 'executes a program'
#接着是想要什么
class Synthesizer:
def __init__(self, name):
self.name = name
def __str__(self):
return "the {} synthesizer".format(self.name)
def play(self):
return "is playing an electronic song"
class Human:
def __init__(self, name):
self.name = name
def __str__(self):
return "{} the human".format(self.name)
def speak(self):
return "says hello"
"""
这时客户端仅知道如何调用execute()方法,并不知道play()和speak(),
在不改变Synthesizer和Human类的前提下,该如何使代码更有效?
"""
# 建造适配器
class Adapter():
def __init__(self, obj, adapted_methods):
self.obj = obj
self.__dict__.update(adapted_methods)
def __str__(self):
return str(self.obj)
# 使用适配器
def main():
objects = [Computer("Asus")]
synth = Synthesizer("moog")
objects.append(Adapter(synth, dict(execute=synth.play)))
human = Human("Bob")
objects.append(Adapter(human,dict(execute=human.speak)))
for i in objects:
print("{} {}".format(str(i), i.execute()))
if __name__ == '__main__':
main()
2. 修饰器模式
当我们想对一个对象添加额外的功能时,通常有以下不同的可选方法:
- 如果合理,可以直接将功能添加到对象所属的类
- 使用组合
- 使用继承
与继承相比,通常应该优先选择组合,因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。
设计模式提供了第四种方法:支持动态地(运行时)扩展一个对象的功能。
使用修饰器模式使斐波那契数列和计算前n个数的和更加快速。
from functools import wraps
def memoize(fn):
known = dict()
@wraps(fn)
def memeoizer(*args):
if args not in known:
known[args] = fn(*args)
return known[args]
return memeoizer
@memoize
def nsum(n):
"""返回前n个数字的和"""
assert (n>=0)
return 0 if n==0 else n+nsum(n-1)
@memoize
def fibonacci(n):
"""返回斐波那契数列的第n个数"""
assert (n>=0)
return n if n==0 or n==1 else fibonacci(n-1)+fibonacci(n-2)
if __name__ == "__main__":
from timeit import Timer
measure = [
{'exec':'fibonacci(3)', 'import':'fibonacci', 'func':fibonacci},
{'exec':'nsum(2)', 'import':'nsum', 'func':nsum}
]
for m in measure:
t = Timer("{}".format(m["exec"]),"from __main__ import {}".format(m["import"]))
print("name:{},doc:{},executing:{},time:{}".format(m["func"].__name__,m["func"].__doc__,m["exec"],t.timeit()))
3. 外观模式
外观模式又叫做门面模式,在面向过程程序设计中,解耦是一种推崇的理念。但事实上由于某些系统过于复杂,从而增加了客户端与子系统之间的耦合度。例如:在家观看多媒体影院时,更希望按下一个按钮就能实现影碟机、电视、音响的协同工作,而不是让每个机器都要操作一遍。这种情况下可以实现外观模式,即引入一个类对子系统进行包装,让客户端与其进行交互。
外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
仅将Computer类暴露给客户端,客户端仅执行Computer的start()方法,所有其他的复杂方法都由外观类Computer维护。
from enum import Enum
from abc import ABCMeta, abstractmethod
State = Enum("state","new running sleeping restart zobie")
class Server(metaclass=ABCMeta):
@abstractmethod
def __init__(self):
pass
def __str__(self):
return self.name
@abstractmethod
def boot(self):
pass
@abstractmethod
def kill(self,restart=True):
pass
class FileServer(Server):
def __init__(self):
"""初始化文件服务进程要求的操作"""
self.name = "FileServer"
self.state = State.new
def boot(self):
print("booting the {}".format(self))
"""启动文件服务进程要求的操作"""
self.state = State.running
def kill(self,restart=True):
print("Killing {}".format(self))
"""终止文件服务进程要求的操作"""
self.state = State.restart if restart else State.zombie
def create_file(self, user, name, permission):
"""检查访问权限的有效性,用户权限等"""
print("trying to create the file '{}' with permissions {}".format(name, user, permission))
class ProcessServer(Server):
def __init__(self):
"""初始化进程服务进程要求的操作"""
self.name = "ProcessServer"
self.state = State.new
def boot(self):
print("booting the {}".format(self))
"""启动进程服务进程要求的操作"""
self.state = State.running
def kill(self,restart=True):
print("Killing {}".format(self))
"""终止进程服务进程要求的操作"""
self.state = State.restart if restart else State.zombie
def create_process(self, user, name):
"""检查用户权限和生成PID等"""
print("trying to create the process '{}' for user '{}'".format(name,user))
class OperatingSystem:
"""外观"""
def __init__(self):
self.fs = FileServer()
self.ps = ProcessServer()
def start(self):
[i.boot() for i in (self.fs, self.ps)]
def creat_file(self, user, name, permissions):
return self.fs.create_file(user, name, permissions)
def create_process(self, user, name):
return self.ps.create_process(user, name)
def main():
os = OperatingSystem()
os.start()
os.creat_file("foo", "hello", "-rw-r-r")
os.create_process("bar", "ls /temp")
if __name__ == '__main__':
main()
使用外观类OperationSystem,客户端代码可以创建文件和进程,而无需知道操作系统的内部细节。
4. 享元模式
享元模式:通过为相似对象引入数据共享来最小化内存使用,提升性能。
一个享元(Flyweight)就是一个包含状态独立的不可变(又称固有的)数据的共享对象,依赖状态的可变(又称非固有的)数据不应是享元的一部分,因为每个对象的这种信息不同,无法共享。如果享元需要非固有数据,应该由客户端代码显示地提供。
示例:
在设计游戏中,两个团队的所有士兵都有一些共同的数据,比如,挑起、低头等,这些数据可以创建一个享元来包含所有共有的数据。
士兵也有一些因人而异的数据,比如强制,健康状态等,这些数据不是共享的一部分
享元旨在优化性能和内存使用。所有嵌入式系统(手机、平板电脑等)和性能关键的应用(游戏、3D图形处理和实时系统等)都能从中获益
使用享元模式的条件:
·应用需要使用大量的对象
·对象太多,存储/渲染他们的代价太大。一旦移除对象中的可变状态,多组不同的对象可被相对更少的共享对象所替代。
·对象ID对于应用不重要。对象共享会造成ID比较的失败,所以不能依赖对象ID
import random
from enum import Enum
TreeType = Enum("TreeType","apple_tree cherry_tree peach_tree")
class Tree:
pool = dict()
def __new__(cls, tree_type):
obj = cls.pool.get(tree_type, None)
if not obj:
obj = object.__new__(cls)
cls.pool[tree_type] = obj
obj.tree_type = tree_type
return obj
def render(self, age, x, y):
print("render a tree of type {} and age {} at ({},{})".format(self.tree_type, age, x, y))
def main():
rnd = random.Random()
age_min, age_max = 1, 30 #单位为年
min_point, max_point = 0, 100
tree_counter = 0
for _ in range(10):
t1 = Tree(TreeType.apple_tree)
t1.render(rnd.randint(age_min, age_max),
rnd.randint(min_point, max_point),
rnd.randint(min_point, max_point)
)
tree_counter += 1
for _ in range(3):
t2 = Tree(TreeType.cherry_tree)
t2.render(rnd.randint(age_min, age_max),
rnd.randint(min_point, max_point),
rnd.randint(min_point, max_point)
)
tree_counter += 1
for _ in range(5):
t3 = Tree(TreeType.peach_tree)
t3.render(rnd.randint(age_min, age_max),
rnd.randint(min_point, max_point),
rnd.randint(min_point, max_point)
)
tree_counter += 1
print("trees rendered:{}".format(tree_counter))
print("trees actually created:{}".format(len(Tree.pool)))
t4 = Tree(TreeType.cherry_tree)
t5 = Tree(TreeType.cherry_tree)
t6 = Tree(TreeType.apple_tree)
print("{} == {}? {}".format(id(t4), id(t5), id(t4) == id(t5)))
print("{} == {}? {}".format(id(t5), id(t6), id(t5) == id(t6)))
if __name__ == '__main__':
main()
5. 模型-视图-控制模式
关注点分离(Separation of Concerns,SoC)原则是软件工程相关的设计原则之一。SoC原则背后的思想是将一个应用且分为不同的部分,每个部分解决一个单独的关注点。
模型-视图-控制器 (Model-View-Controller,MVC)模式是应用到面向对象编程的SoC原则。
模型是核心,代表应用的信息本院,包含和管理(业务)逻辑、数据、状态以及应用的规则。视图是模型的可视化表现,只展示数据,并不处理数据。控制器是模型与视图之间的链接/粘附。模型与视图之间的所有通信东通过控制器进行。
示例:
用户输入数字,然后就能看到与数字相关的,名人名言。名人名言存储在quotes元组中,通常这些数据存储在数据库、文件或其他地方只有模型能够直接访问。
quotes = ("A man is not complete until he is married.Then he is finished..",
'As I said before, I never repeat myself.',
'Behind a successful man is an exhausted woman.',
'Black holes really suck...', 'Facts are stubborn things.'
)
# 模型
class QuoteModel():
def get_quote(self, n):
try:
value = quotes[n]
except IndexError as err:
value = "Not found!"
return value
#视图
class QuoteTerminalView:
def show(self,quote):
print("And the quote is:'{}'".format(quote))
def error(self,msg):
print("Error:{}".format(msg))
def select_quote(self):
return input("请选择一个选项:")
# 控制器
class QuoteTerminalController():
def __init__(self):
self.model = QuoteModel()
self.view = QuoteTerminalView()
def run(self):
valid_input = False
while not valid_input:
n = self.view.select_quote()
try:
n = int(n)
except ValueError as err:
self.view.error("Incorrect index '{}'".format(n))
else:
valid_input = True
quote = self.model.get_quote(n)
self.view.show(quote)
# 启动函数
def main():
controller = QuoteTerminalController()
while True:
controller.run()
if __name__ =="__main__":
main()
6. 代理模式
当我们想要把一个计算成本较高的对象的创建过程延迟到用户首次真正使用它的时候才进行创建。
这类操作通常使用代理设计模式(Proxy design pattern)来实现。该模式因为使用代理对象在访问实际对象之前执行重要操作而得其名。
比较知名的代理有四种类型:
·远程代理:实际存在于不同地址空间(例如,某个网络服务器)的对象在本地的代理者。
·虚拟代理:用于濑初始化,将一个大计算量对象的创建延迟到真正需要的时候进行。
·保护/防护代理:控制对敏感对象的访问。
·智能(引用)代理:在对象被访问时执行额外的动作。此类代理的例子包括引用计数和线程安全检查。
在面向对象编程[OOP(Object Oriented Programming)],有两种基本的、不同类型的濑初始化,如下:
·在实例级:意味着会对一个对象的特性进行濑初识化,但该特性有一个对象作用域。同一个类的每个实例(对象)都有自己的(不同的)特性副本。
·在类级或模块级:在这种情况下,我们不希望每个实例都有一个不同的特性的副本,而是所有的实例同享同一个特性,而特性是濑初始化的。
代理的应用案例:
1、在使用私有网络或云搭建一个分布式系统时。在分布式系统中,一些对象存在于本地内
存中,一些对象存在于远程计算机的内存中。如果我们不想本地代码关心两者之间的区
别,那么可以创建一个远程代理来隐藏/封装,使得应用的分布式性质透明化。
2、因过早创建计算成本较高的对象导致应用遭受性能问题之时。使用虚拟代理引入懒初始化,仅在真正需要对象之时才创建,能够明显提高性能。
3、用于检查一个用户是否有足够权限来访问某个信息片段。如果应用要处理敏感信息(例如,医疗数据),我们会希望确保用户在被准许之后才能访问/修改数据。一个保护/防护代理可以处理所有安全相关的行为。
4、应用(或库、工具集、框架等)使用多线程,而我们希望把线程安全的重任从客户端代码转移到应用。这种情况下,可以创建一个智能代理,对客户端隐藏线程安全的复杂性。
5、对象关系映射(Object-Relational Mapping,ORM)API也是一个如何使用远程代理的例子。包括Django在内的许多流行Web框架使用一个ORM来提供类OOP的关系型数据库访问。ORM是关系型数据库的代理,数据库可以部署在任意地方,本地或远程服务器都可以。
完整代理模式示例:
"""
示例:设置保护代理实现查看用户和添加用户
·查看用户列表:这一操作不需要特殊权限
·添加新用户:这一操作要求客户端提供一个特殊的密码
"""
class SensitiveInfo():
def __init__(self):
self.users = ["nick", "tom", "ben", "mike"]
def read(self):
print("有{}个用户:{}".format(len(self.users)," ".join(self.users)))
def add(self, user):
self.users.append(user)
print("添加用户{}".format(user))
class Info:
"""SensitiveInfo的保护代理"""
def __init__(self):
self.proteced = SensitiveInfo()
self.secret = "root"
def read(self):
self.proteced.read()
def add(self,user):
sec = input("请输入密码:")
if self.secret == sec:
self.proteced.add(user)
else:
print("密码错误!")
def main():
info = Info()
while True:
print("""1:查看用户
2:添加用户
3:quit""")
key = input("请输入选项:")
if key == "1":
info.read()
elif key== "2":
name = input("请输入用户名:")
info.add(name)
elif key == "3":
exit()
else:
print("选项有误,请重新选择!")
if __name__ == "__main__":
main()
(3)行为型模式
1. 责任链模式
责任链(Chain of Responsibility)模式用于让多个对象来处理单个请求时,或者用于预先不知道应该由哪个对象(来自某个对象链)来处理某个特定请求时。原则如下:
① 存在一个对象链(链表、树或任何其他便捷的数据结构)。
② 我们一开始将请求发送给链中的第一个对象。
③ 对象决定其是否要处理该请求。
④ 对象将请求转发给下一个对象。
⑤ 重复该过程,直到到达链尾。
注意:
客户端代码仅知道第一个要处理的元素,而非拥有对其所有元素的引用,并且每一个处理元素仅知道其直接的下一个邻居(称为后继),而不知道所有其他处理的元素。也被称为:单向链表。
责任链模式可以参考生活中的ATM机槽口:
可以放入:100,50,20,10,5,1,。槽口接收完钱之后由不同的容器对比。钞票返回时则是从适当的容器中取。
# Event类描述一个事件
class Event():
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
# Widget是一个核心应用类
class Widget:
def __init__(self,parent=None):
self.parent = parent
# 动态分发,通过hasattr和getattr决定请求由谁处理
def handle(self,event):
handler = 'handle_{}'.format(event)
if hasattr(self, handler):
method = getattr(self, handler)
elif self.parent:
self.parent.handle(event)
elif hasattr(self, "handle_default"):
self.handle_default(event)
# 具有不同行为的控件,处理close和defalut事件
class MAinWindow(Widget):
def handle_close(self, event):
print("MianWindow:{}".format(event))
def handle_default(self,event):
print("MainWindow Default:{}".format(event))
# 处理paint事件
class SenDialog(Widget):
def handle_paint(self, event):
print("SenDialog:{}".format(event))
# 处理down事件
class MsgText(Widget):
def handle_down(self,event):
print("MsgText:{}".format(event))
# main函数展示创建控件和事件,以及控件如何对事件做出反应
def main():
mw = MAinWindow()
sd = SenDialog(mw)
msg = MsgText(sd)
for e in ('down', 'paint', 'unhandled', 'close'):
evt = Event(e)
print('\nSending event -{}- to MainWindow'.format(evt))
mw.handle(evt)
print('Sending event -{}- to SendDialog'.format(evt))
sd.handle(evt)
print('Sending event -{}- to MsgText'.format(evt))
msg.handle(evt)
if __name__ == '__main__':
main()
2. 命令模式
3. 解释器模式
4. 观察者模式
5. 状态模式
6. 策略模式
7. 模板模式