结构型设计模式:
结构型设计模式处理一个系统中不同实体(比如,类和对象)之间的关系,关注的是提供一种简单的对象组合方式来创造新功能。可用于实现不兼容软件之间的接口兼容。
①.适配器模式
简介:
适配器模式(Adapter pattern)是一种结构型设计模式,帮助我们实现两个不兼容接口之间的兼容。
.
解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但 又并非总是能修改代码,或因为我们无法访问这些代码(例如,组件以外部库的方式提供),或因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器。
.
电子商务系统是这方面众所周知的例子。假设我们使用的一个电子商务系统中包含一个 calculate_total(order)函数。这个函数计算一个订单的总金额,但货币单位为丹麦克朗(DanishKroner,DKK)。顾客让我们支持更多的流行货币,比如美元(United States Dollar,USD)和欧元(Euro,EUR),这是很合理的要求。如果我们拥有系统的源代码,那么可以扩展系统,方法是添加一些新函数,将金额从DKK转换成USD,或者从DKK转换成EUR。但是如果应用仅以外部库的方式提供,我们无法访问其源代码,那又该怎么办呢?在这种情况下,我们仍然可以使用这个外部库(例如,调用它的方法),但无法修改和扩展它。解决方案是编写一个包装器(又名适配器)将数据从给定的DKK格式转换成期望的USD或EUR格式。
现实生活的例子:
我们所有人每天都在使用适配器模式,只不过是硬件上的,而不是软件上的。例如:USB适配器、电源插头适配器
软件的例子:
Grok是一个Python框架,运行在Zope3之上,专注于敏捷开发。Grok框架使用适配器,让已有对象无需变更就能符合指定API的标准
Python第三方包Traits也使用了适配器模式,将没有实现某个指定接口(或一组接口)的对象 转换成实现了接口的对象。
应用案例:
在某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,则可以使用适配器模式。通常两个不兼容接口中的一个是他方的或者是老旧的。 如果一个接口是他方的,就意味着我们无法访问其源代码。如果是老旧的,那么对其重构通常是不切实际的。更进一步,我们可以说修改一个老旧组件的实现以满足我们的需求,不仅是不切实际的,而且也违反了开放封闭原则。
.
开放封闭原则(open/close principle)是面向对象设计的基本原则之一(SOLID中的O),声明一个软件实体应该对扩展是开 放的,对修改则是封闭的。本质上这意味着我们应该无需修改一个软件实体的源代码就能扩展其行为。适配器模式遵从开放/封闭原则。
.
因此,在某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,使用适配器是一种更好的方式,原因如下所示:
1. 不要求访问他方接口的源代码
2. 不违反开放/封闭原则
代码实现:
class Synthesizer:
"""
在Synthesizer类 中,主要动作由play()方法执行。
"""
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:
"""
在Human类中,主要动作由speak()方法执行
"""
def __init__(self, name):
self.name = name
def __str__(self):
return '{} the human'.format(self.name)
def speak(self):
return 'says hello'
class Computer:
"""
用来显示一台计算机的基本信息
"""
def __init__(self, name):
self.name = name
def __str__(self):
return 'the {} computer'.format(self.name)
def execute(self):
"""
execute方法是计算机可以执行的主要动作。这一方法由客户端代码调用。
:return: str
"""
return 'executes a program'
class Adapter:
"""
客户端仅知道如何调用execute()方法,并不知道play()和speak()。在不改变Synthesizer和Human类的前提下,我们该如何做才能
让代码有效?适配器是救星!我们创建一个通用的Adapter类,将一些带不同接口的对象适配到一个统一接口中。__init__()方法的obj参数
是我们想要适配的对象,adapted_methods是一个字典,键值对中的键是客户端要调用的方法,值是应该被调用的方法。
"""
def __init__(self, obj, execute=None, **kwargs):
self.obj = obj
self.execute = execute
self.__dict__.update(kwargs)
def __str__(self):
return str(self.obj)
def main():
objects = [Computer('Asus')]
synth = Synthesizer('moog')
objects.append(Adapter(synth, execute=synth.play, **synth.__dict__))
human = Human('Bob')
objects.append(Adapter(human, execute=human.speak, **human.__dict__))
for i in objects:
print('{} {}'.format(str(i), i.execute()))
for i in objects:
print(i.name)
if __name__ == "__main__":
main()
适配器模式总结
我们使用适配器模式让两个(或多个)不兼容接口兼容。
.
适配器让一件产品在制造出来之后需要应对新需求之时还能工作。Python框架Grok和第三方包Traits各自都使用了适配器模式来获得API一致性和接口兼容性。开放/封闭原则与这些方面密切相关。
.
适配器模式的使用,无需修改不兼容模型的源代码就能获得接口的一致性。这是通过让一个通用的适配器类完成相关工作而实现的。虽然在Python中我们可以沿袭传统方式使用子类(继承)来实现适配器模式,但这种技术是一种很棒的替代方案。
②.修饰器模式:
简介:
无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法。
1. 如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法)
2. 使用组合
3. 使用继承
.
与继承相比,通常应该优先选择组合,因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例
.
修饰器模式:
设计模式为我们提供第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,这种方法就是修饰器。修饰器(Decorator)模式能够以透明的方式(不会影响其他对象)动态地将功能添加到一个对象中。
.
在许多编程语言中,使用子类化(继承)来实现修饰器模式。在Python中,我们可以(并且应该)使用内置的修饰器特性。一个Python修饰器就是对Python语法的一个特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。从实现的角度来说,Python修饰器是一个可调用对象(函数、方法、类),接受一个函数对象fin作为输入,并返回另一个函数对象fout。这意味着可以将任何具有这些属性的可调用对象当作一个修饰器。
现实生活的例子:
修饰器模式通常用于扩展一个对象的功能。这类扩展的实际例子有,给枪加一个消音器、使用不同的照相机镜头(在可拆卸镜头的照相机上)等。
软件的例子:
Django框架大量地使用修饰器,其中一个例子是视图修饰器。Django的视图(View)修饰器可用于以下几种用途:
1. 限制某些HTTP请求对视图的访问
2. 控制特定视图上的缓存行为
3. 按单个视图控制压缩
4. 基于特定HTTP请求头控制缓存
.
Grok框架也使用修饰器来实现不同的目标,比如下面几种情况:
1. 将一个函数注册为事件订阅者
2. 以特定权限保护一个方法
3. 实现适配器模式
应用案例:
当用于实现横切关注点(cross-cutting concerns)时,修饰器模式会大显神威。以下是横切关注点的一些例子。:
1. 数据校验
2. 事务处理(这里的事务类似于数据库事务,意味着要么所有步骤都成功完成,要么事务失败)
3. 缓存
4. 日志
5. 监控
6. 调试
7. 业务规则
8. 压缩
9. 加密
.
横切关注点的概念:
一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看作横切关注点。
.
使用修饰器模式的另一个常见例子是图形用户界面工具集。在一个GUI工具集中,我们希望能够将一些特性,比如边框、阴影、颜色以及滚屏,添加到单个组件/部件。
代码实现:
Python修饰器通用并且非常强大。你可以在Python官网python.org的修饰器代码库页面中找到许多修饰器的使用样例。我们将学习如何实现一个memoization修饰器。所有递归函数都能因memoization而提速,那么来试试常用的斐波那契数列例子。使用递归算法实现斐波那契数列,直接了当,但性能问题较大,即使对于很小的数值也是如此。
Low版:
def fibonacci(n):
assert (n >= 0), 'n must be >= 0'
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
from timeit import Timer
t = Timer("fibonacci(8)", 'from __main__ import fibonacci')
print(t.timeit())
普通版:
known = {0: 0, 1: 1}
def fibonacci(n):
assert (n >= 0), 'n must be >= 0'
if n in known:
return known[n]
res = fibonacci(n - 1) + fibonacci(n - 2)
known[n] = res
return res
if __name__ == '__main__':
from timeit import Timer
t = Timer('fibonacci(8)', 'from __main__ import fibonacci')
print(t.timeit())
print(fibonacci(8))
修饰器版
import functools
def memoize(fn):
known = dict()
@functools.wraps(fn)
def memoizer(*args):
if args not in known:
known[args] = fn(*args)
return known[args]
return memoizer
@memoize
def nsum(n):
'''返回前n个数字的和'''
assert (n >= 0), 'n must be >= 0'
return 0 if n == 0 else n + nsum(n - 1)
@memoize
def fibonacci(n):
'''返回斐波那契数列的第n个数'''
assert (n >= 0), 'n must be >= 0'
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
from timeit import Timer
measure = [{'exec': 'fibonacci(100)', 'import': 'fibonacci', 'func': fibonacci},
{'exec': 'nsum(200)', '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()))
修饰器模式总结:
我们使用修饰器模式来扩展一个对象的行为,无需使用继承,非常方便。Python进一步扩展了修饰器的概念,允许我们无需使用继承或组合就能扩展任意可调用对象(函数、方法或类)的行为。我们可以使用Python内置的修饰器特性。
.
修饰器模式是实现横切关注点的绝佳方案,因为横切关注点通用但不太适合使用面向对象编程范式来实现。修饰器可以帮助我们保持函数简洁,同时不牺牲性能。
③.外观模式
简介:
外观设计隐藏了系统的内部复杂性,并通过一个简化的接口向客户端暴露必要的部分。本质上,外观(Facade)是在已有复杂系统之上实现的一个抽象层。
现实生活的例子:
企业的客服部门,汽车或摩托车的启动钥匙、计算机、电视等通过一个简单按钮就能激活的复杂电子设备
软件的例子:
django-oscar-datacash模块是Django的一个第三方组件,用于集成DataCash支付网关。该组件有一个Gateway类,提供对多种DataCash API的细粒度访问。在那之上,它也包含一个Facade类,提供粗粒度API(提供给那些不需要处理细节的人),并针对审计目的提供保存事务的能力。
.
Caliendo是一个用于模拟Python API的的接口,它包含一个facade模块。该模块使用外观模式来完成许多不同但有用的事情(比如缓存方法),并基于传给顶层Facade方法的输入对象决定返回什么方法。
应用案例:
使用外观模式的最常见理由是为一个复杂系统提供单个简单的入口点。引入外观之后,客户端代码通过简单地调用一个方法/函数就能使用一个系统。同时,内部系统并不会丢失任何功能, 外观只是封装了内部系统。
.
不把系统的内部功能暴露给客户端代码有一个额外的好处:我们可以改变系统内部,但客户端代码不用关心这个改变,也不会受这个改变的影响。客户端代码不需要进行任何改变
.
如果你的系统包含多层,外观模式也能派上用场。你可以为每一层引入一个外观入口点,并让所有层级通过它们的外观相互通信。这提高了层级之间的松耦合性,尽可能保持层级独立
代码实现:
from enum import Enum
from abc import ABCMeta, abstractmethod
State = Enum('State', 'new running sleeping restart zombie')
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):
"""
服务进程FileServer除了Server接口要求实现的方法之外,还有一个create_file()方法用于创建文件。
"""
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, permissions):
'''检查访问权限的有效性、用户权限等'''
print("trying to create the file '{}' for user '{}' with permissions {}".format
(name, user, permissions))
class ProcessServer(Server):
"""
服务进程ProcessServer除了Server接口要求实现的方法之外,还有一个create_process()方法用于创建进程。
"""
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:
"""
OperatingSystem类是一个外观。__init__()中创建所有需要的服务进程实例。
start()方法是系统的入口点,供客户端代码使用。如果需要,可以添加更多的包装方法作为服务的访问点,
比如包装方法create_file()和create_process()。从客户端的角度来看,
所有服务都是由OperatingSystem类提供的。客户端并不应该被不必要的细节所干扰,
比如,服务进程的存在和每个服务进程的责任。
"""
def __init__(self):
self.fs = FileServer()
self.ps = ProcessServer()
def start(self):
[i.boot() for i in (self.fs, self.ps)]
def create_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.create_file('foo', 'hello', '-rw-r-r')
os.create_process('bar', 'ls /tmp')
if __name__ == '__main__':
main()
外观模式总结:
在客户端代码想要使用一个复杂系统但又不关心系统复杂性之时,这种模式是为复杂系统提供一个简单接口的理想方式。一台计算机是一个外观, 因为当我们使用它时需要做的事情仅是按一个按钮来启动它;其余的所有硬件复杂性都用户无感知地交由BIOS、引导加载程序以及其他系统软件来处理。现实生活中外观的例子更多,比如,我们所致电的银行或公司客服部门,还有启动机动车所使用的钥匙。
.
我们讨论了两个使用外观的Django第三方组件:django-oscar-datacash和Caliendo。前者使用外观模式来提供一个简单的DataCash API以及保存事务的能力,后者为多种目的使用了外观,比如,缓存、基于输入对象的类型决定应该返回什么。
④.享元模式:
简介:
享元设计模式通过为相似对象引入数据共享来小化内存使用,提升性能。一个享元(Flyweight)就是一个包含状态独立的不可变(又称固有的)数据的共享对象。
现实生活的例子:
享元模式是一个用于优化的设计模式。因此,要找一个合适的现实生活的例子不太容易。我们可以把享元看作现实生活中的缓存区。例如,许多书店都有专用的书架来摆放新和流行的出版物。这就是一个缓存区,你可以先在这些专用书架上看看有没有正在找的书籍,如果没找到, 则可以让图书管理员来帮你。
软件的例子:
Exaile音乐播放器使用享元来复用通过相同URL识别的对象(在这里是指音乐歌曲)。创建一个与已有对象的URL相同的新对象是没有意义的,所以复用相同 的对象来节约资源。Peppy是一个用Python语言实现的类XEmacs编辑器,它使用享元模式存储major mode状态栏的状态。这是因为除非用户修改,否则所有状态栏共享相同的属性。
应用案例:
享元旨在优化性能和内存使用。所有嵌入式系统(手机、平板电脑、游戏终端和微控制器等)和性能关键的应用(游戏、3D图形处理和实时系统等)都能从其获益。若想要享元模式有效,需要满足GoF的《设计模式》一书罗列的以下几个条件:
1. 应用需要使用大量的对象。
2. 对象太多,存储/渲染它们的代价太大。一旦移除对象中的可变状态(因为在需要之时,应该由客户端代码显式地传递给享元),多组不同的对象可被相对更少的共享对象所替代。
3. 对象ID对于应用不重要。对象共享会造成ID比较的失败,所以不能依赖对象ID(那些在客户端代码看来不同的对象,终具有相同的ID)
代码实现:
import random
from enum import Enum
# 个Enum类型变量描述三种不同种类的水果树
Treetype = Enum('TreeType', 'apple_tree cherry_tree peach_tree')
class Tree:
"""
pool变量是一个对象池(换句话说,是我们的缓存)。注意:pool是一个类属性(类的所有实例共享的一个变量。
使用特殊方法__new__(这个方法在__init__之 前被调用),我们把Tree类变换成一个元类,元类支持自引用。
这意味着cls引用的是Tree类。当客户端要创建Tree的一个实例时,会以tree_type参数传递树的种 类。树的种类用
于检查是否创建过相同种类的树。如果是,则返回之前创建的对象;否则,将这个新的树种添加到池中,并返回相应的新对象
"""
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):
"""
方法render()用于在屏幕上渲染一棵树。
"""
print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))
def main():
rnd = random.Random()
# 一棵树的年龄是1到30年之间的一个随机值。
age_min, age_max = 1, 30 # 单位为年
# 坐标使用1到100之间的随机值
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()
享元模式的小结:
在我们想要优化内存使用提高应用性能之时,可以使用享元。在所有内存受限(想一想嵌入式系统)或关注性能的系统(比如图形软件和电子游戏)中,这一点相当重要。基于GTK+的Exaile音乐播放器使用享元来避免对象复制,Peppy文本编辑器则使用享元来共享状态栏的属性。
.
一般来说,在应用需要创建大量的计算代价大但共享许多属性的对象时,可以使用享元。重点在于将不可变(可共享)的属性与可变的属性区分开。我们实现了一个树渲染器,支持三种不同的树家族。通过显式地向render方法提供可变的年龄和x,y属性,我们成功地仅创建了3个不同的对象,而不是18个。
⑤.模型—视图—控制器模式:
简介:
关注点分离(Separation of Concerns,SoC)原则是软件工程相关的设计原则之一。SoC原则背后的思想是将一个应用切分成不同的部分,每个部分解决一个单独的关注点。。分层设计中的层次(数据访问层、业务逻辑层和表示层等)即是关注点的例子。使用SoC原则能简化软件应用的开发和维护。
.
模型—视图—控制器(Model-View-Controller,MVC)模式是应用到面向对象编程的Soc原则。
.
MVC 被认为是一种架构模式而不是一种设计模式。架构模式与设计模式之间的区别在于前者比后者的 范畴更广。
1. 模型是核心的部分,代表着应用的信息本源,包含和管理(业务)逻辑、数据、状态以及应用的规则。
2. 视图是模型的可视化表现。视图的例子有,计算机图形用户界面、计算机终端的文本输出、智能手机的应用图形界面、PDF文档、饼 图和柱状图等。视图只是展示数据,并不处理数据。
3. 控制器是模型与视图之间的链接/粘附。模型与视图之间的所有通信都通过控制器进行
.
MVC的优势:
无需修改模型就能使用多个视图的能力(甚至可以根据需要同时使用多个视图)。为了实现模型与其表现之间的解耦,每个视图通常都需要属于它的控制器。如果模型直接与特定视图通信,我们将无法对同一个模型使用多个视图(或者至少无法以简洁模块化的方式实现)。
现实生活的例子:
你造一栋新房子,通常会请不同的专业人员来完成安装管道和电路 、粉刷房子等。。。
软件的例子:
Web框架web2py是一个支持MVC模式的轻量级Python框架
Django也是一个MVC框架,但是它使用了不同的命名约定。在此约定下,控制器被称为视图,视图被称为模板。Django使用名称模型—模板—视图(Model-Template-View,MTV)来替代MVC。
应用案例:
MVC是一个非常通用且大有用处的设计模式。这一模式提供了以下这些好处:
1. 视图与模型的分离允许美工一心搞UI部分,程序员一心搞开发,不会相互干扰。
2. 由于视图与模型之间的松耦合,每个部分可以单独修改或者扩展,不会相互影响。例如,添加一个新视图的成本很小,只要 为其实现一个控制器就可以了。
3. 因为职责明晰,维护每个部分也更简单。
代码实现:
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:
"""
模型极为简约,只有一个get_quote()方法,基于索引n从quotes元组中返回对应的名人名言(字符串)。
"""
def get_quote(self, n):
try:
value = quotes[n]
except IndexError as err:
value = 'Not found!'
return value
class QuoteTerminalView:
"""
视图有三个方法,分别是show()、error()和select_quote()。
"""
def show(self, quote):
"""
show()用于在屏幕上输 出一句名人名言(或者输出提示信息Not found!);
"""
print('And the quote is: "{}"'.format(quote))
def error(self, msg):
"""
error()用于在屏幕上输出一条错误消息;
"""
print('Error: {}'.format(msg))
def select_quote(self):
"""
select_quote()用于读取用户的选择
"""
return input('Which quote number would you like to see? ')
class QuoteTerminalController:
"""
控制器负责协调
"""
def __init__(self):
"""
__init__()方法初始化模型和视图
"""
self.model = QuoteModel()
self.view = QuoteTerminalView()
def run(self):
"""
run()方法校验用户提供的名言索 引,然后从模型中获取名言,并返回给视图展示
"""
valid_input = False
while not valid_input:
try:
n = self.view.select_quote()
n = int(n)
vaild_input = True
except ValueError as err:
self.view.error("Incorrect index '{}'".format(n))
quote = self.model.get_quote(n)
self.view.show(quote)
def main():
controller = QuoteTerminalController()
while True:
controller.run()
if __name__ == '__main__':
main()
MVC模式总结:
在从头开始实现MVC时,请确保创建的模型很智能,控制器很瘦,视图很傻瓜。
.
- 可以将具有以下功能的模型视为智能模型。
1. 包含所有的校验/业务规则/逻辑
2. 处理应用的状态
3. 访问应用数据(数据库、云或其他)
4. 不依赖UI
.
- 可以将符合以下条件的控制器视为瘦控制器。
1. 在用户与视图交互时,更新模型
2. 在模型改变时,更新视图
3. 如果需要,在数据传递给模型/视图之前进行处理
4. 不展示数据
5. 不直接访问应用数据
6. 不包含校验/业务规则/逻辑
.
- 可以将符合以下条件的视图视为傻瓜视图。
1. 展示数据
2. 允许用户与其交互
3. 仅做小的数据处理,通常由一种模板语言提供处理能力(例如,使用简单的变量和循环控制)
4. 不存储任何数据
5. 不直接访问应用数据
6. 不包含校验/业务规则/逻辑
.
MVC是一个非常重要的设计模式,用于将应用组织成三个 部分:模型、视图和控制器。
.
每个部分都有明确的职责。模型负责访问数据,管理应用的状态。视图是模型的外在表现。视图并非必须是图形化的;文本输出也是一种好视图。控制器是模型与视图之间的连接。MVC的恰当使用能确保终产出的应用易于维护、易于扩展。
.
Python框架web2py使用MVC作为核心架构理念。即使是简单的web2py例子也使用了MVC来实现模块化和可维护性。Django也是一个MVC框架,但它使用的名称是MTV。
.
使用MVC时,请确保创建智能的模型(核心功能)、瘦控制器(实现视图与模型之间通信的能力)以及傻瓜式的视图(外在表现,小化逻辑处理)。
⑥.代理模式
简介:
在某些应用中,我们想要在访问某个对象之前执行一个或多个重要的操作,例如,访问敏感信息——在允许用户访问敏感信息之前,我们希望确保用户具备足够的权限。操作系统中也存在类似的情况,用户必须具有管理员权限才能在系统中安装新程序,这类操作通常使用代理设计模式(Proxy design pattern)来实现。
.
以下是四种不同的知名代理类型:
1. 远程代理:实际存在于不同地址空间(例如,某个网络服务器)的对象在本地的代理者。
2. 虚拟代理:用于懒初始化,将一个大计算量对象的创建延迟到真正需要的时候进行。
3. 保护/防护代理:控制对敏感对象的访问。
4. 智能(引用)代理:在对象被访问时执行额外的动作。此类代理的例子包括引用计数和线程安全检查。
现实生活的例子:
芯片(又名芯片密码)卡是现实生活中使用防护代理的一个好例子。借记或信用卡包含一个芯片,ATM机或读卡器需要先读取芯片;在芯片通过验证后, 需要一个密码(PIN)才能完成交易。这意味着只有在物理地提供芯片卡并且知道密码时才能进行交易。
.
使用银行支票替代现金进行购买和交易是远程代理的一个例子。支票准许了对一个银行账户 的访问。
软件的例子:
Python的weakref模块包含一个proxy()方法,该方法接受一个输入对象并将一个智能代理返回给该对象。弱引用是为对象添加引用计数支持的一种推荐方式。
.
ZeroMQ是一组专注于分布式计算的自由开源软件项目。ZeroMQ的Python实现有一个代理模块,实现了一个远程代理。该模块允许Tornado的处理程序在不同的远程进程中运行。
代码实现:
保护代理演示
class SensitiveInfo:
"""
SensitiveInfo类包含我们希望保护的信息。
users变量是已有用户的列表。
read()方法 输出用户列表。
add()方法将一个新用户添加到列表中。
"""
def __init__(self):
self.users = ['nick', 'tom', 'ben', 'mike']
def read(self):
print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))
def add(self, user):
self.users.append(user)
print('Added user {}'.format(user))
class Info:
"""
Info类是SensitiveInfo的一个保护代理。secret变量值是客户端代码在添加新用户时被要求告知/提供的密码。
注意,这只是一个例子。现实中,永远不要执行以下操作:
在源代码中存储密码
以明文形式存储密码
使用一种弱(例如,MD5)或自定义加密形式
read()方法是SensetiveInfo.read()的一个包装。
add()方法确保仅当客户端代码知道 密码时才能添加新用户
"""
def __init__(self):
self.protected = SensitiveInfo()
self.secret = '111111'
def read(self):
self.protected.read()
def add(self, user):
sec = input('what is the secret? ')
self.protected.add(user) if sec == self.secret else print("That's wrong!")
def main():
"""
main()函数展示了客户端代码可以如何使用代理模式。客户端代码创建一个Info类的实例,
并使用菜单让用户选择来读取列表、添加新用户或退出应用。
"""
info = Info()
while True:
print('1. read list |==| 2. add user |==| 3. quit')
key = input('choose option: ')
if key == '1':
info.read()
elif key == '2':
name = input('choose username: ')
info.add(name)
elif key == '3':
exit()
else:
print('unknown option: {}'.format(key))
if __name__ == '__main__':
main()
虚拟代理演示:
class LazyProperty:
def __init__(self, method):
self.method = method
self.method_name = method.__name__
print('function overriden: {}'.format(self.method))
print("function's name: {}".format(self.method_name))
def __get__(self, obj, cls):
if not obj:
return None
value = self.method(obj)
print('value {}'.format(value))
setattr(obj, self.method_name, value)
return value
class Test:
def __init__(self):
self.x = 'foo'
self.y = 'bar'
self._resource = None
@LazyProperty
def resource(self):
print('initializing self._resource which is: {}'.format(self._resource))
self._resource = tuple(range(5)) # 假设这一行的计算成本比较大
return self._resource
def main():
t = Test()
print(t.x)
print(t.y)
"""做更多的事情……"""
print(t.resource)
print(t.resource)
print(t.resource)
if __name__ == '__main__':
main()
应用案例:
因为存在至少四种常见的代理类型,所以代理设计模式有很多应用案例,如下所示:
.
1. 在使用私有网络或云搭建一个分布式系统时。在分布式系统中,一些对象存在于本地内存中,一些对象存在于远程计算机的内存 中。如果我们不想本地代码关心两者之间的区别,那么可以创建一个远程代理来隐藏或封装,使得应用的分布式性质透明化。
2. 因过早创建计算成本较高的对象导致应用遭受性能问题之时。使用虚拟代理引入懒初始化,仅在真正需要对象之时才创建,能够 明显提高性能。
3. 用于检查一个用户是否有足够权限来访问某个信息片段。如果应用要处理敏感信息(例如,医疗数据),我们会希望确保用户在 被准许之后才能访问/修改数据。一个保护/防护 代理可以处理所有安全相关的行为。
4. 应用(或库、工具集、框架等)使用多线程,而我们希望把线程安全的重任从客户端代码转移到应用。这种情况下,可以创建一个 智能代理,对客户端隐藏线程安全的复杂性。
5. 对象关系映射(Object-Relational Mapping,ORM)API也是一个如何使用远程代理的例子。包括Django在内的许多流行Web 框架使用一个ORM来提供类OOP的关系型数据库访问。ORM是关系型数据库的代理,数据库可以部署在任意地方,本地或远程服务 器都可以。
代理模式小结:
我们使用代理模式实现一个实际类的替代品,这样可以在访问实际类之前(或之后)做一些额外的事情。
.
芯片卡和银行支票是人们每天都在使用的两个不同代理的例子。芯片卡是一个防护代理,而银行支票是一个远程代理。另外,一些流行软件中也使用代理。Python有一个weakref.proxy()方法,使得创建一个智能代理非常简单。ZeroMQ的Python实现则使用了远程代理。
.
我们讨论了几个代理模式的应用案例,包括性能、安全及向用户提供简单的API。在第二个代码示例中,我们实现一个保护代理来处理用户信息。这个例子可以以多种方式进行改进,特别是关于其安全缺陷和用户列表实际上未持久化(永久存储)的问题。