Python 高手编程系列五百零九:结构型模式

结构型模式在大型应用中非常重要。它们决定代码的组织方式,并告诉开发人员如何
与应用程序的每个部分进行交互。
很长一段时间以来,在 Python 中,Zope 项目中的 Zope 组件架构(Zope ComponentArchitecture,ZCA)提供了最知名的许多结构型模式的实现。它实现了本节中描述的大多
数模式,并提供了一组丰富的工具来处理它们。ZCA 不仅可以在 Zope 框架中运行,而且
也可以在其他框架(如 Twisted)中运行。它提供了接口和适配器的实现。
不幸的是(或许不是),Zope 失去了几乎所有的势头,并不如以前那样流行。但是它的
ZCA 可能仍然是在 Python 中实现结构型模式很好的参考。Baiju Muthukadan 创建了 Zope 组件
架构的综合指南(A Comprehensive Guide to Zope Component Architecture)。可以免费地打印或
者在线阅读它(参见 http://muthukadan.net/docs/zca.html)。该书写于 2009 年,所以它没有涵盖
Python 的最新版本,但它仍然是一份很好的资料,因为它提供了很多上述模式的基本原理。
Python 已经通过其语法提供了一些流行的结构型模式。例如,类和函数装饰器可以被
认为是装饰器模式(decorator pattern)的应用。此外,支持创建和导入模块是模块模式
(module pattern)的一种发散。
常见的结构模式有很多。最初的设计模式(Design Patterns)书中描述了 7 种,并且后
续其他文献扩展也对这些模式进行了扩展。我们不会讨论所有的模式,主要关注 3 个最受
欢迎并且公认的模式,它们是:
• 适配器(adapter);
• 代理(proxy);
• 外观(facade)。
适配器
使用适配器(adapter)模式可以在另一个接口中使用现有类的接口。换句话说,适配器包
装一个类或一个对象 A,以便它能在目标上下文中工作,这可以是一个类或者一个对象 B。
在 Python 中创建适配器实际上是非常简单的,这归应于这种语言中的类型工作原理。
Python 中的类型原理通常被称为鸭子类型(duck-typing):
“如果它走路像鸭子,说话像鸭子,那就它就是鸭子!”
根据这个规则,函数或方法接受的值,不应该取决于它的类型,而应基于其接口。所
以,只要对象的行为正如预期的那样,即具有适当的方法签名和属性,它的类型被认为是
兼容的。这与许多静态类型语言是完全不同的,在这些语言里这种事情几乎是不可行的。
实际上,当某些代码用于处理给定的类时,只要它们提供了代码使用的方法和属性,
就可以向它提供来自另一个类的对象。当然,这假设代码不会调用 instance 来验证实例
是否是特定类的实例。
适配器模式基于这个原理,并定义了一个包装机制,其中包装类或对象,以使其在主
要不是为它工作的上下文中工作。StringIO 是一个典型的例子,虽然它适配 str 类型,
同样它可以作为 file 类型如下所示:

from io import StringIO
my_file = StringIO(‘some content’)
my_file.read()
‘some content’
my_file.seek(0)
my_file.read(1)
‘s’
让我们举另一个例子。DublinCoreInfos 类显示给定文档的都柏林核心集(Dublin
Core)信息的一些子集的摘要(参见 http://dublincore.org/),该信息由一个 dict 提供。它读
取几个字段,如作者的姓名或标题,并打印它们。为了能够显示文件的都柏林核心集,必须
以 StringIO 同样的方式进行修改。
DublinCoreAdapter 包装一个文件实例,并通过它提供元数据访问如下所示:
from os.path import split, splitext
class DublinCoreAdapter:
def init(self, filename):
self.filename = filename
@property
def title(self):
return splitext(split(self.filename)[-1])[0]
@property
def languages(self):
return (‘en’,)
def getitem(self, item):
return getattr(self, item, ‘Unknown’)
class DublinCoreInfo(object):
def summary(self, dc_dict):
print(‘Title: %s’ % dc_dict[‘title’])
print(‘Creator: %s’ % dc_dict[‘creator’])
print(‘Languages: %s’ % ', '.join(dc_dict[‘languages’]))
以下是使用示例:
adapted = DublinCoreAdapter(‘example.txt’)
infos = DublinCoreInfo()
infos.summary(adapted)
Title: example
Creator: Unknown
Languages: en
除此之外它允许替换,适配器模式也可以改变开发人员的工作方式。使对象在特定上
下文中工作使得假设对象的类不再重要。重要的是,这个类实现了 DublinCoreInfo 正
在等待的行为,这个行为是由适配器修复或完成的。因此,代码可以,只是设法,告诉它
是否与实现特定行为的对象兼容。这可以由接口表示。
接口
接口(interface)主要进行 API 的定义。它描述了一个应该实现需要的行为的类的方法
和属性的列表。这个描述不实现任何代码,只是为希望实现接口的任何类定义了显式契约。
任何类都可以以任何想要的方式实现一个或多个接口。
虽然 Python 更喜欢使用鸭子类型,而不是显式接口定义,但有时后者可能更好。例如,
显式接口定义使框架更容易定义接口上的功能。
好处是类是松耦合的,这被认为是一个好的做法。例如,为了执行给定的过程,类 A
不依赖于类 B,而是依赖于接口 I.类 B 实现了 I,但它可以是任何其他类。
在许多静态类型的语言中都内置了对这种技术的支持,例如 Java 或 Go。接口允许函
数或方法限制实现给定接口的可接受参数对象的范围,无论它来自哪个类。这比将参数限
制到给定类型或其子类更灵活。它像鸭子类型行为的显式版本:Java 使用接口在编译时验
证类型安全性,而不是在运行时使用鸭子类型将事物绑定在一起。
与 Java 相比,Python 有一个完全不同的类型原理,所以它没有本地支持的接口。无论
如何,如果你想对应用程序接口有更明确的控制,通常有两种解决方案可供选择。
• 使用一些添加接口概念的第三方框架。
• 使用一些高级语言特性来构建处理接口的方法。
(1)使用 zope.interface
你可以使用一些框架在 Python 中构建显式接口。最值得注意的一个是 Zope 项目的一部分。就是 zope.interface 包。虽然,现在,Zope 不像以前那么流行,zope.interface
包仍然是 Twisted 框架的主要组件之一。
zope.interface 包的核心类是 Interface 类。你可以通过子类化来显式地定义一
个新的接口。让我们假设我们要为矩形的每个实现定义强制性接口,如下所示:
from zope.interface import Interface, Attribute
class IRectangle(Interface):
width = Attribute(“The width of rectangle”)
height = Attribute(“The height of rectangle”)
def area():
“”" 返回矩形的面积
“”"
def perimeter():
“”" 返回矩形的周长
“”"
使用 zope.interface 定义接口时需要注意的一些重要事项如下。
• 接口的常用命名约定是使用 I 作为名称后缀。
• 接口的方法不能使用 self 参数。
• 由于接口不提供具体的实现,它应该只包含空方法。你可以使用 pass 语句,抛出
NotImplementedError,或提供 docstring(首选)。
• 接口还可以使用 Attribute 类指定所需的属性。
当你定义了这样的约定时,你可以定义新的具体类,为我们的 IRectangle 接口提供实
现。为了做到这一点,你需要使用 implementer()类装饰器,并实现所有定义的方法和
属性如下所示:
@implementer(IRectangle)
class Square:
“”" 使用 rectangle 接口的正方形实现
“”"
def init(self, size):
self.size = size
@property
def width(self):
return self.size
@property
def height(self):
return self.size
def area(self):
return self.size ** 2
def perimeter(self):
return 4 * self.size
@implementer(IRectangle)
class Rectangle:
“”" 矩形的具体实现
“”"
def init(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return self.width * 2 + self.height * 2
通常,接口定义了具体实现需要满足的约定。此设计模式的主要优点是能够在使用对象
之前验证约定和实现之间的一致性。使用普通的鸭子类型方法,只有当运行时缺少属性或方
法时,才会发现不一致。使用 zope.interface,你可以使用 zope.interface.verify
模块中的两个方法内省实际实现,已在早期发现不一致。
• verifyClass(interface,class_object):这将验证类对象是否存在方法
以及它们的签名的正确性,无需查找属性。
• verifyObject(interface,instance):它验证方法,它们的签名以及实际
对象实例的属性。
由于我们已经定义了我们的接口和两个具体的实现,让我们在以下交互式会话中验证
它们的约定:
from zope.interface.verify import verifyClass, verifyObject
verifyObject(IRectangle, Square(2))
True
verifyClass(IRectangle, Square)

True
verifyObject(IRectangle, Rectangle(2, 2))
True
verifyClass(IRectangle, Rectangle)
True
没有什么令人印象深刻。Rectangle 和 Square 类仔细地遵循定义的约定,所以除
了成功验证之外,没有什么可做的了。但是当我们犯错误时会发生什么?让我们看一个没
有提供完整的 IRectangle 接口实现的两个类的例子如下所示:
@implementer(IRectangle)
class Point:
def init(self, x, y):
self.x = x
self.y = y
@implementer(IRectangle)
class Circle:
def init(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
Point 类不提供 IRectangle 接口的任何方法或属性,因此其验证将显示类级别上
已有的不一致如下所示:
verifyClass(IRectangle, Point)
Traceback (most recent call last):
File “”, line 1, in
File “zope/interface/verify.py”, line 102, in verifyClass
return verify(iface, candidate, tentative, vtype=‘c’)
File “zope/interface/verify.py”, line 62, in verify
raise BrokenImplementation(iface, name)
zope.interface.exceptions.BrokenImplementation: An object has failed to
implement interface

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值