本文通过 Python 代码示例,介绍了常用的软件设计模式,包括单例模式、工厂模式、原型模式及其深浅拷贝对比、建造者模式和适配器模式。
1. 单例模式 当项目有几个部分不会影响代码的执行时,(例如日志记录)那么使用全局状态是可以接受的
避免使用全局状态的原因之一就是不想使 项目一部分中的代码修改全局状态
logger_class.py
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 class Logger (): def __init__ (self, filename ): self .filename = filename def _write_log (self, level, msg ): with open (self .filename , 'a' ) as log_file: log_file.write('{0} {1}\n' .format (level, msg)) def critical (self, msg ): self ._write_log('CRUTICAL' ,msg) def error (self, msg ): self ._write_log('ERROR' ,msg) def warn (self, msg ): self ._write_log('WARN' ,msg) def info (self, msg ): self ._write_log('INFO' , msg) def debug (self, msg ): self ._write_log('DEBUG' ,msg)
main_script.py
1 2 3 4 5 6 7 8 9 10 from logger_class import Loggerlogger_object = Logger('filename_class' ) logger_object.info( 'hahahaha' )
__init__函数并不是真正意义上的构造函数,__init__方法做的事情是在对象创建好之后初始化变量。真正创建实例的是__new__方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Singleton (object ): ''' 实现__new__方法 并在将一个类的实例绑定到类变量_instance上, 如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回 如果cls._instance不为None,直接返回cls._instance ''' _instance = None def __new__ (cls, *args, **kwargs ): if cls._instance is None : cls._instance = object .__new__(cls, *args, **kwargs) return cls._instance s1 = Singleton() s2 = Singleton() print (s1)print (s2)
这个写法能跑,但有两个没说出来的坑。
一是 __init__ 每次实例化都会执行。 __new__ 返回的是同一个对象,可 type.__call__ 紧接着还会调一次 obj.__init__(*args),于是后一次构造会重置 共享实例的状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 class S : _instance = None def __new__ (cls, *a, **k ): if cls._instance is None : cls._instance = object .__new__(cls) return cls._instance def __init__ (self, name ): self .name = name a = S('first' ) b = S('second' ) a is b a.name
想避免就得在 __init__ 里自己加初始化标记,或者干脆把初始化逻辑挪进 __new__ 的分支里。
二是 object.__new__(cls, *args, **kwargs) 一旦真的带上参数就会报错。
也就是说上面那份代码只在”永远无参构造”的情况下侥幸成立。正确写法是 object.__new__(cls),参数交给 __init__ 去接。
实践上,Python 里实现单例通常有更省事的三种做法:
写法
适用场景
代价
模块级对象
绝大多数情况。模块本身只会被导入一次,天然单例
不能延迟初始化
@functools.lru_cache 装在工厂函数上
想按参数缓存不同实例(严格说是 multiton)
参数必须可哈希
元类里重写 __call__
要让”单例”这件事对子类也生效
心智负担最重
其中模块级对象是最该优先考虑的——它没有上面那两个坑,也不需要任何技巧。
2. 工厂模式 当希望通过一个通用接口来创建对象,而不是让创建代码分散在整个系统中
创建一个集中式的系统来进行对象创建的一种方式就是使用工厂模式
原型模式不需要子类化,不过需要一个初始化操作,而工厂模式需要子类化,但不需要初始化
工厂模式一个简单的实现 原始代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import pygamewindows_dimensions = 800 ,600 screen = pygame.display.set_mode(windows_dimensions) x,y = 100 , 100 player_quits = False while not player_quits: for event in pygame.event.get(): if event.type == pygame.QUIT: player_quits = True pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: y -=4 if pressed[pygame.K_DOWN]: y += 4 if pressed[pygame.K_LEFT]: x -= 4 if pressed[pygame.K_RIGHT]: x += 4 screen.fill((0 , 0 , 0 )) pygame.draw.rect(screen,(255 , 255 , 0 ), pygame.Rect(x, y, 20 , 20 )) pygame.display.flip()
改写,方便扩展
shape_factory.py
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import pygameimport timeclass Shape (): def __init__ (self,x ,y ): self .x = x self .y = y def draw (self ): raise NotImplementedError() def move (self, direction ): if direction == 'up' : self .y -= 4 if direction == 'down' : self .y += 4 if direction == 'left' : self .x -= 4 if direction == 'right' : self .x += 4 @staticmethod def factory (type ): if type == 'Circle' : return Circle(100 , 100 ) if type == 'Square' : return Square(100 , 100 ) raise ValueError(f'Bad shape requested: {type } ' ) class Square (Shape ): def draw (self ): pygame.draw.rect(screen,(255 , 255 , 0 ), pygame.Rect(self .x, self .y, 20 , 20 )) class Circle (Shape ): def draw (self ): pygame.draw.circle(screen, (0 , 255 , 255 ), (self .x, self .y),10 ) if __name__ == '__main__' : windows_dimensions = 800 ,600 screen = pygame.display.set_mode(windows_dimensions) obj = Shape.factory('Circle' ) player_quits = False while not player_quits: for event in pygame.event.get(): if event.type == pygame.QUIT: player_quits = True pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: obj.move('up' ) if pressed[pygame.K_DOWN]: obj.move('down' ) if pressed[pygame.K_LEFT]: obj.move('left' ) if pressed[pygame.K_RIGHT]: obj.move('right' ) screen.fill((0 , 0 , 0 )) obj.draw() pygame.display.flip()
抽象工厂 当希望创建单个接口来访问整个工厂集合时,可以使用一个抽象工厂
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 import abcclass AbstractFactory (metaclass=abc.ABCMeta): @abc.abstractmethod def make_object (self ): return class CircleFactory (AbstractFactory ): def make_object (self ): return Circle() class SquareFactory (AbstractFactory ): def make_object (self ): return Square() def draw_function (factory ): drawable = factory.make_object() drawable.draw() def prepare_client (): squareFactory = SquareFactory() draw_function(squareFactory) circleFactory = CircleFactory() draw_function(circleFactory)
3. 原型模式 在原型模式中,优先使用组合而非继承。组成的类使我们可以在运行时替换那些组成部分,从而彻底改善系统的可测试性和可维护性。
原型模式在一般情况下的样子 声明一个抽象基类
prototype_1.py
1 2 3 4 5 6 from abc import ABCMeta, abstractmethodclass Prototype (metaclass=ABCMeta): @abstractmethod def clone (self ): pass
调用
concrete.py
1 2 3 4 5 6 from prototype_1 import Prototypefrom copy import deepcopyclass Concrete (Prototype ): def clone (self ): return deepcopy(self )
扩展该抽象基类时,会强制实现clone方法。
4. 原型模式/python中的浅拷贝与深拷贝的对比 python 浅拷贝与深拷贝 python的引用计数
int float str tuple 为不可变对象
list dict set 为可变对象
引用计数 是 CPython 对所有 对象的内存管理方式,可变不可变一视同仁,不是不可变对象的专属待遇。
另外别把”值相同的不可变对象一定共用一份内存”当成规律——那只在几种特殊情况下成立:小整数(-5 ~ 256)被预先缓存、字面量和标识符样式的字符串在编译期被 intern、以及编译期能算出结果的常量折叠。运行时才算出来的等值对象,通常是两个独立对象:
1 2 3 4 int ('1000' ) is int ('1000' ) int ('256' ) is int ('256' ) '' .join(['a' , 'b' , 'c' ]) is 'abc' 'abc' is 'ab' + 'c'
下面这段用 copy.copy 举证其实说明不了共用内存的问题——copy 对字符串这类原子的不可变类型是直接把原对象返回,压根没打算复制:
1 2 3 4 5 6 7 8 9 10 import copya = 'weilai' b = a c = copy.copy(a) if id (a) == id (b) == id (c): print (f'all id is {id (a)} ' ) else : print (f'id(a) is {id (a)} ' ) print (f'id(b) is {id (b)} ' ) print (f'id(c) is {id (c)} ' )
浅拷贝 浅拷贝会创建一个新的对象,对于对象中的元素,他依然会引用原来的物体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import copya = ['weilai' , 'handsome' ] b = a c = copy.copy(a) if id (a) == id (b) == id (c): print (f'all id is {id (a)} ' ) else : print (f'id(a) is {id (a)} ' ) print (f'id(b) is {id (b)} ' ) print (f'id(c) is {id (c)} ' ) a.append('cool' ) print (a)print (b)print (c)
由于浅拷贝会使用原始元素的引用(内存地址),所以在操作对象内部的可变元素时,其结果是会影响到拷贝对象的
1 2 3 4 5 6 7 8 9 import copya = [[1 , 2 , 3 ], 3 , 4 ] b = a c = copy.copy(a) a[0 ][0 ] = 'a' a[1 ] = 'b' print (a) print (b) print (c)
深拷贝 深拷贝遇到可变对象,则又会进行一层对象的创建,操作被考对象内部的可变对象,不影响拷贝对象内部的值。
1 2 3 4 5 6 7 8 9 import copya = [[1 , 2 , 3 ], 3 , 4 ] b = a c = copy.deepcopy(a) a[0 ][0 ] = 'a' a[1 ] = 'b' print (a) print (b) print (c)
总结 python变量实际上是指向相关值在内存中存储位置的指针。 拷贝可变对象时,深浅拷贝都会新建一个顶层对象,和源对象占用不同的内存空间;但字符串、整数这类原子的不可变对象是个例外,copy 和 deepcopy 都直接把原对象还回来,也就是最上面那个例子里 id(a) == id(c) 的情形。
如果源对象只有一级目录(元素都是不可变的),源对象做任何改动都不影响深浅拷贝对象
如果源对象不止一级目录,要看改动落在哪一层:改嵌套可变对象的内部 (a[0][0] = 'a')会反映到浅拷贝上,因为那个内层列表两边共用;给顶层元素重新赋值 (a[1] = 'b')则不会,浅拷贝自己有一份顶层的槽位。上面的例子正是如此,c 是 [['a', 2, 3], 3, 4]——内层跟着变了,顶层第二个元素还是 3。深拷贝两种改动都不受影响。
序列对象的切片其实是浅拷贝,即只拷贝顶级的对象
5. 建造者模式 建造者模式,将复杂对象的构造从其表示形式中分离了出来,因此可用相同的构造过程来创建不同形式的表单 。
其最大的缺点在于,我们要为希望创建的每一种产品类型创建ConcreteBuilder建造者
原始代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def generator_webform (text_field_list=[],checkbox_field_list=[] ): generator_fields = "\n" .join(map (lambda x:'{0}:<br> <input type="text" name="{0}"> <br>' .format (x),text_field_list)) generator_fields += "\n" .join(map (lambda x:'<label><input type="checkbox" id="{0}" value={0}>{0} <br>' .format (x),checkbox_field_list)) return "<form>{fields}</form>" .format (fields=generator_fields) def build_html_form (text_field_list=[],checkbox_field_list=[] ): with open ('form_file.html' ,'w' ) as f : f.write('<html><body>{}</body></html>' .format (generator_webform(text_field_list=text_fields,checkbox_field_list=checkbox_fields))) if __name__ == "__main__" : text_fields = ["name" ,"age" ,"email" ,"telephone" ] checkbox_fields = ['awesome' , 'bad' ] print (generator_webform(text_field_list=text_fields,checkbox_field_list=checkbox_fields)) build_html_form(text_field_list=text_fields,checkbox_field_list=checkbox_fields)
改造后
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 from abc import ABCMeta, abstractmethodclass Director (object , metaclass=ABCMeta): def __init__ (self ): self ._builder =None def set_build (self, builder ): self ._builder = builder @abstractmethod def construct (self, field_list ): pass def get_constructed_object (self ): return self ._builder.constructed_object class AbstractFormBuilder (object ,metaclass=ABCMeta): def __init__ (self ): self .constructed_object = None @abstractmethod def add_checkbox (self, checkbox_dict ): pass @abstractmethod def add_text_field (self, field_dict ): pass @abstractmethod def add_button (self, button_dict ): pass class HtmlForm (object ): def __init__ (self ): self .field_list = [] def __repr__ (self ): return '<form>{}</form>' .format ('' .join(self .field_list)) class HtmlFormBuilder (AbstractFormBuilder ): def __init__ (self ): self .constructed_object =HtmlForm() def add_text_field (self, field_dict ): self .constructed_object.field_list.append( '{0}:<br><input type="text" name="{1}"><br>' .format (field_dict['label' ], field_dict['field_name' ])) def add_checkbox (self, checkbox_dict ): self .constructed_object.field_list.append( '<label><input type="checkbox" id="{0}" value = "{1}"> {2}<br>' .format ('field_id' , checkbox_dict['value' ], checkbox_dict['label' ])) def add_button (self, button_dict ): self .constructed_object.field_list.append( '<button type="button"> {} </button>' .format (button_dict['text' ])) class FormDirector (Director ): def __init__ (self ): Director.__init__(self ) def construct (self, field_list ): for field in field_list: if field["field_type" ] == 'text_field' : self ._builder.add_text_field(field) elif field["field_type" ] == 'checkbox' : self ._builder.add_checkbox(field) elif field["field_type" ] == 'button' : self ._builder.add_button(field) if __name__ == '__main__' : director = FormDirector() html_form_builder = HtmlFormBuilder() director.set_build(html_form_builder) field_list = [ { 'field_type' : 'text_field' , 'label' : 'Best text you have ever writen' , 'field_name' : 'Field One' }, { 'field_type' : 'checkbox' , 'field_id' : 'check_it' , 'label' : 'check for on' , 'value' : '1' }, { 'field_type' : 'text_field' , 'label' : 'another text you have ever writen' , 'field_name' : 'Field One' }, { 'field_type' : 'button' , 'text' : 'Done' } ] director.construct(field_list) with open ('form_file.html' , 'w' ) as f: f.write( '<html><body>{0!r}</body></html>' .format ( director.get_constructed_object() ) )
6. 适配器模式 适配器模式具有以下要素:
目标 –定义客户端所使用的特定于 领域的接口
客户端 – 使用遵从于 目标接口的对象
适配者类 –由于对象不遵从目标而必须修改的接口
适配器 – 将适配者类中所具有的接口 修改为我们想要在客户端中 使用的接口的代码
1.定义我们希望适应的组成部分是什么 2.识别出客户端需要的接口 3.设计和实现适配器以便将客户端所需的接口映射到适配者类所提供的接口
客户端从适配者类中解耦出来并且被耦合到接口,这样实现了可扩展性和可维护性
不要重复自己
面向接口 而非 面向现实进行编程
支持对象组合,而非继承
关注点分离 将系统分割为 单独单元 并让每个单元尤其自己的关注点。单元彼此越独立,系统的维护和扩展也会变的越容易。
1 2 3 4 5 6 7 class ObjectAdapter (object ): def __init__ (self, what_i_have, provided_function ): self .what_i_have = what_i_have self .required_function = provided_function def __getattr__ (self, attr ): return getattr (self .what_i_have, attr)