本文通过 Python 代码示例,介绍了常用的软件设计模式,包括单例模式、工厂模式、原型模式及其深浅拷贝对比、建造者模式和适配器模式。
1. 单例模式
title: 单例模式
date: 2019-07-02 11:05:31
tags:
categories: python
当项目有几个部分不会影响代码的执行时,(例如日志记录)那么使用全局状态是可以接受的
避免使用全局状态的原因之一就是不想使 项目一部分中的代码修改全局状态
logger_class.py1
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## def log_message(msg):
## with open( 'filename.log', 'a' ) as log_file:
## log_file.write(f"{msg}\n") # 代替 format,python3新特性
##
## log_message('save this for later')
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.py1
2
3
4
5
6
7
8
9
10## import logger
##
##
## for i in range(4):
## logger.log_message(f'log massger {i}')
from logger_class import Logger
logger_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
18class 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)
2. 工厂模式
title: 工厂模式
date: 2019-07-14 11:05:31
tags:
categories: python练习
当希望通过一个通用接口来创建对象,而不是让创建代码分散在整个系统中
创建一个集中式的系统来进行对象创建的一种方式就是使用工厂模式
原型模式不需要子类化,不过需要一个初始化操作,而工厂模式需要子类化,但不需要初始化
工厂模式一个简单的实现
原始代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import pygame
windows_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.py1
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
60import pygame
import time
## pygame.init()
class 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): # 工厂模式,可在这里加type类型
if type == 'Circle':
return Circle(100, 100)
if type == 'Square':
return Square(100, 100)
assert 0, '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
28import abc
class AbstractFactory(): # 抽象工厂会定义这些具体工厂的结构,之后这些具体工厂会创建本例中的原型和正方形
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def make_object(self):
return
class CircleFactory(AbstractFactory):
def make_object(self):
# do something
return Circle()
class SquareFactory(AbstractFactory):
def make_object(self):
# do something
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. 原型模式
title: 原型模式
date: 2019-07-14 11:05:31
tags:
categories: python练习
在原型模式中,优先使用组合而非继承。组成的类使我们可以在运行时替换那些组成部分,从而彻底改善系统的可测试性和可维护性。
原型模式在一般情况下的样子
声明一个抽象基类
prototype_1.py1
2
3
4
5
6from abc import ABCMeta, abstractmethod
class Prototype(metaclass=ABCMeta):
@abstractmethod
def clone(self):
pass
调用
concrete.py1
2
3
4
5
6from prototype_1 import Prototype
from copy import deepcopy
class Concrete(Prototype):
def clone(self):
return deepcopy(self)
**扩展该抽象基类时,会强制实现clone方法。
4. 原型模式/python中的浅拷贝与深拷贝的对比
python 浅拷贝与深拷贝
python的引用计数
- int float str tuple 为不可变对象
- list dict set 为可变对象
python 内不可变对象的内存管理方式是引用计数,python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数。1
2
3
4
5
6
7
8
9
10import copy
a = '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
15import copy
a = ['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)}') # c创建了一个新对象,指向原来的 a
a.append('cool')
print(a)
print(b)
print(c) # c 还是['weilai', 'handsome']
由于浅拷贝会使用原始元素的引用(内存地址),所以在操作对象内部的可变元素时,其结果是会影响到拷贝对象的
1 | import copy |
深拷贝
**深拷贝遇到可变对象,则又会进行一层对象的创建,操作被考对象内部的可变对象,不影响拷贝对象内部的值。1
2
3
4
5
6
7
8
9import copy
a = [[1, 2, 3], 3, 4]
b = a
c = copy.deepcopy(a) # [[1, 2, 3], 3, 4]
a[0][0] = 'a'
a[1] = 'b'
print(a) # [['a', 2, 3], 'b', 4]
print(b) # [['a', 2, 3], 'b', 4]
print(c) # [[1, 2, 3], 3, 4]
总结
python变量实际上是指向相关值在内存中存储位置的指针 。 深浅拷贝都是对源对象的复制,占用不同的内存空间
- 如果源对象只有一级目录的话,源对象做任何改动,不影响深浅拷贝对象
- 如果源对象不止一级目录的话,源对象做任何改动,都要影响浅拷贝,但不影响深拷贝
- 序列对象的切片其实是浅拷贝,即只拷贝顶级的对象
5. 建造者模式
建造者模式,将复杂对象的构造从其表示形式中分离了出来,因此可用相同的构造过程来创建不同形式的表单。
其最大的缺点在于,我们要为希望创建的每一种产品类型创建ConcreteBuilder建造者
原始代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def 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
107from abc import ABCMeta, abstractmethod
class 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_dict(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.filed_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.filed_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.filed_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_check(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 | class ObjectAdapter(object): |