文章源文:http://twistedmatrix.com/documents/current/core/howto/components.html
twisted是用python开发的一个事件驱动的网络引擎,可以在 python 编写的程序中使用,便利的创建各种网络应用。
本文是阅读 twisted 文档过程中发现的一篇解释了其 Interface 和 Adapter 设计理念的文章,值得一译留作笔记。
正文:
Components: Interfaces and Adapters
面向对象编程语言允许程序员创建已有类的子类对象以此重用已有的部分代码。当一个B类作为另一个A类的子类时,我们称它继承了A类的所有行为(behaviour)。于是B类就可以重载(override)或者扩展(extend)这些A类的行为。继承在很多情况下都非常的有用,但是正由于它如此的方便易用,在大型的软件系统中继承很容易被滥用,尤其是牵扯到多重继承时。一种解决方案是使用“委托(delegation)”来代替继承。委托就是简单的要求一个对象为另一个对象执行某个任务。为了支持这种设计,经常要使用“元件(components)”,“接口(interface )”和“适配器(adapter)”由 Zope 3组开发。
接口只是一种简单的标记,由此对象来表明实现了此接口。在此之后其他的对象就可以请求“请给我一个为了对象类型Y而实现了接口X的对象”,为了某种类型的对象从而实现了接口的对象称为适配器。
父类和子类的关系被成为“是”的关系,子类的对象可以说“是”一个父类的对象。举例如下:
- class Shape:
- sideLength = 0
- def getSideLength(self):
- return self.sideLength
-
- def setSideLength(self, sideLength):
- self.sideLength = sideLength
- def area(self):
- raise NotImplementedError, "Subclasses must implement area"
-
- class Triangle(Shape):
- def area(self):
- return (self.sideLength * self.sideLength) / 2
- class Square(Shape):
- def area(self):
- return self.sideLength * self.sideLength
在上例中,Triangle “是”一个 Shape,所以它继承了 Shape,同时 Square 也“是”一个 Shape,所以它也继承了 Shape。
但是,继承可以变得复杂,尤其当多继承牵扯进来。多继承允许一个类继承于多个基类。那些严重依赖于多继承的软件通常都拥有非常广并且非常深的继承树。既然多继承意味着“实现(implementation)”的继承,因此定位一个方法的实际实现和确定哪一个方法被调用将很复杂。举例如下:
- class Area:
- sideLength = 0
- def getSideLength(self):
- return self.sideLength
-
- def setSideLength(self, sideLength):
- self.sideLength = sideLength
- def area(self):
- raise NotImplementedError, "Subclasses must implement area"
- class Color:
- color = None
- def setColor(self, color):
- self.color = color
-
- def getColor(self):
- return self.color
- class Square(Area, Color):
- def area(self):
- return self.sideLength * self.sideLength
程序员使用“实现继承(implementation inheritance)”的目的是为了使代码更容易阅读,因为它可以使 Area 类的的实现细节和 Color 类的实现分离开。因为一个对象可能拥有颜色但是没有区域,或者情况反之。问题的关键在于一个正方形不是一个区域或者 一个颜色而是拥有区域和颜色。于是我们应该使用另外一种面向对象技术--“组成 (composition)”,它依赖于委托机制而不是继承机制将代码分割为小的可重用的块。继续使用上面多继承的例子来说明问题。
如果 Color 和 Area 类都定义同样的一个方法,比如 calculate ?实现将追溯到哪个?实现的映射可能因为程序员通过重构系统中的一些类而改变,产生模糊的 bug 。我们的第一想法可能是改变 calculate 的方法名以此来避免名字冲突,但是这样的做法可能需要在系统中改变相当多的名字,而且如果在整合两个系统的时候也会遇到很多问题。
现在我们来考虑另一个例子。我们有一个吹风机类,实现的是美国电压。我们有两个电源接口,其中之一是美国的110伏特,另外一种是外国的220伏特接口。如果我们将吹风机插入220伏的接口就会发生错误。将吹风机类实现同时支持110伏和220伏接口的方法将很枯燥,而且如果我们现在要插入另外的一种接口呢?例子:
- class HairDryer:
- def plug(self, socket):
- if socket.voltage() == 110:
- print "I was plugged in properly and am operating."
- else:
- print "I was plugged in improperly and "
- print "now you have no hair dryer any more."
- class AmericanSocket:
- def voltage(self):
- return 110
- class ForeignSocket:
- def voltage(self):
- return 220
给出了这些类,可以实施以下操作:
- >>> hd = HairDryer()
- >>> am = AmericanSocket()
- >>> hd.plug(am)
- I was plugged in properly and am operating.
- >>> fs = ForeignSocket()
- >>> hd.plug(fs)
- I was plugged in improperly and
- now you have no hair dryer any more.
我们试图去解决这个问题。可以编写一个 ForeignSocket 类的适配器,它为美国吹风机转换电压。适配器的构造函数只有唯一的参数--“被适配”的对象。在此例中,我们为了清除将展示所有相关代码:
- class AdaptToAmericanSocket:
- def __init__(self, original):
- self.original = original
-
- def voltage(self):
- return self.original.voltage() / 2
现在,我们可以如此使用:
- >>> hd = HairDryer()
- >>> fs = ForeignSocket()
- >>> adapted = AdaptToAmericanSocket(fs)
- >>> hd.plug(adapted)
- I was plugged in properly and am operating.
如你所看到的那样,适配器“重载”了原本的实现。它还可以扩展原本对象的接口。
适配器是一个将代码分化的非常有用的办法。但是如果没有一些基础设施的话也会有点问题。如果每一个想要使用适配器的代码都必须明确的构造一个它自己的适配器,元件之间的耦合会过分紧密。我们希望去实现松耦合,于是引入了 twisted.python.components。
我们首先需要更具体的讨论接口。如我们之前提到的,接口只是以供用作标记的类。接口必须是 zope.interface.Interface 的子类,并且有一个对于 python 程序员来说奇怪的外观:
- from zope.interface import Interface
- class IAmericanSocket(Interface):
- def voltage():
- """Return the voltage produced by this socket object, as an integer.
- """
注意到它除了继承于 Interface 以外就像一个常规的类定义一般,只是没有没有任何方法实现的实体。因为既然 python 没有像 java 那样对接口的任何原生的语言级的支持,这样的做法可以区别接口和普通类的定义。
现在我们有了一个定义好的接口,我们可以涉及这样使用的类:“实现了 IAmericanSocket 接口的 AmericanSocket 类”和“请给我一个使 ForeignSocket 适配 IAmericanSocket 的对象”。
我们来看看如何声明一个类实现了一个接口:
- from zope.interface import implements
- class AmericanSocket:
- implements(IAmericanSocket)
- def voltage(self):
- return 110
可见,声明一个类实现了一个接口,我们只需要简单的调用 zope.interface.implements 在类层次。
现在我们来实现 AdaptToAmericanSocket 类为一个真正的适配器。这里我们制定它实现了 IAmericanSocket :
- from zope.interface import implements
- class AdaptToAmericanSocket:
- implements(IAmericanSocket)
- def __init__(self, original):
- """
- Pass the original ForeignSocket object as original
- """
- self.original = original
- def voltage(self):
- return self.original.voltage() / 2
到现在为止,我们并没有做什么,因此总是需要去定义更多类型。为了使元件更加有用,我们必须进行“元件注册”。既然 AdapterToAmericanSocket 实现了 IAmericanSocket 并且将 ForeignSocket 对象规范化了,我们可以注册 AdapterToAmericanSocket 作为 ForeignSocket 对 IAmericanSocket 的适配器。从代码可以更清晰的看到这一点:
- from zope.interface import Interface, implements
- from twisted.python import components
- class IAmericanSocket(Interface):
- def voltage():
- """Return the voltage produced by this socket object, as an integer.
- """
-
- class AmericanSocket:
- implements(IAmericanSocket)
- def voltage(self):
- return 110
- class ForeignSocket:
- def voltage(self):
- return 220
- class AdaptToAmericanSocket:
- implements(IAmericanSocket)
- def __init__(self, original):
- self.original = original
- def voltage(self):
- return self.original.voltage() / 2
-
- components.registerAdapter(
- AdaptToAmericanSocket,
- ForeignSocket,
- IAmericanSocket)
现在如果我们在交互解释器里运行这段脚本,我们可以更多的了解到如何使用元件,我们可以做的第一件事实确定一个对象是否实现了某一个接口:
- >>> IAmericanSocket.implementedBy(AmericanSocket)
- True
- >>> IAmericanSocket.implementedBy(ForeignSocket)
- False
- >>> am = AmericanSocket()
- >>> fs = ForeignSocket()
- >>> IAmericanSocket.providedBy(am)
- True
- >>> IAmericanSocket.providedBy(fs)
- False
如你所看到的那样,AmericanSocket 实体声明实现 IAmericanSocket ,但是 ForeignSocket 没有。如果我们让吹风机使用美国接口,我们可以通过检查它是否实现了 IAmericanSocket 接口来指导是否是安全的。但是如果我们要通过 ForeignSocket 来使用吹风机,必须在使用前将它适配到 IAmericanSocket 接口。
我们调用这个接口并带入一个参数,这个接口类会在适配器注册中去查找,寻找为了给定参数的类型而实现了接口的适配器。如果找到了,就会生成一个相应适配器类的对象。如果我们尝试去适配一个已经实现了 IAmericanSocket 接口的对象,那它只是单纯的返回这个对象而已。当然,我们可以写一个聪明的吹风机,去自动寻找不同接口的适配器:
- class HairDryer:
- def plug(self, socket):
- adapted = IAmericanSocket(socket)
- assert adapted.voltage() == 110, "BOOM"
- print "I was plugged in properly and am operating"
这样就可以让吹风机直接插入外国接口了。
阅读(1066) | 评论(0) | 转发(0) |