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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
Chapter 1: Components and Interfaces
Zope is becoming a component system. Zope components will be Python
objects with interfaces that describe them. Right now only some of
the Zope code base uses interfaces. In coming releases more and more
of Zope will include interfaces. As a Zope developer you can use
interfaces right now to build your Zope components.
Zope Components
Components are objects that are associated with interfaces. An
interface is a Python object that describes how you work with
other Python objects. In this chapter, you'll see some simple
examples of creating components, and a description of interfaces
and how they work.
Here is a very simple component that says hello. Like all components,
this one generally consists of two peices, an interface, and an
implementation::
from Interface import Base
class Hello(Base):
""" The Hello interface provides greetings. """
def hello(self, name):
""" Say hello to the name """
class HelloComponent:
__implements__ = Hello
def hello(self, name):
return "hello %s!" % name
Let's take a look at this step by step. Here, you see two Python class
statements. The first statement creates the *interface*, and the
second statement creates the *implementation*.
The first class statement creates the 'Hello' interface. This
interface describes one method, called 'hello'. Notice that there is
no implementation for this method, interfaces do not define behavior,
they just describe a specification.
The second 'class' statement creates the 'HelloComponent' class.
This class is the actual component that *does* what 'Hello'
*describes*. This is usualy referred to as the *implementation*
of 'Hello'. In order for you to know what interfaces
'HelloComponent' implements, it must somehow associate itself with
an interface. The '__implements__' class attribute does just
that. It says, "I implement these interfaces". In this case,
'HelloComponent' asserts that it implements one interface,
'Hello'.
The interface describes how you would work with the object, but it
doesn't dictate how that description is implemented. For example,
here's a more complex implementation of the 'Hello' interface::
import xmlrpclib
class XMLRPCHello:
__implements__ = Hello
def hello(self, name):
"""
Delegates the hello call to a remote object using XML-RPC.
"""
s = xmlrpclib.Server('http://www.zope.org/')
return s.hello(name)
This component contacts a remote server and gets its hello
greeting from a remote component.
And that's all there is to components, really. The rest of this
chapter describes interfaces and how you can work with them from
the perspective of components. In Chapter 3, we'll put all this
together into a Zope product.
Python Interfaces
Interface describe the behavior of an object by containing useful
information about the object. This information includes:
o Prose documentation about the object. In Python terms, this is
called the "doc string" of the interface. In this element, you
describe how the object works in prose language and any other
useful information about the object.
o Descriptions of attributes. Attribute descriptions include the
name of the attribute and prose documentation describing the
attributes usage.
o Descriptions of methods. Method descriptions can include:
o Prose "doc string" documentation about the method and its usage.
o A sequence of parameter objects that describes the parameters
expected by the method.
o Optional tagged data. Interface objects (and their
attributes, methods, and method parameters) can have optional,
application specific tagged data associated with them.
Examples uses for this are security assertions, pre/post
conditions, unit tests, and other possible information you may
want to associate with an Interface or its attributes.
Not all of this information is mandatory. For example, you may only
want the methods of your interface to have prose documentation and not
describe the arguments of the method in exact detail. Interface
objects are flexible and let you give or take any of these
components.
Why Use Interfaces?
Interfaces solve a number of problems that arise while developing
large systems with lots of developers.
o Developers waste a lot of time looking at the source code of your
system to figure out how objects work. This is even worse if
someone else has already wasted their time doing the same thing.
o Developers who are new to your system may misunderstand how your
object works, causing, and possibly propagating, usage errors.
o Because an object's interface is inferred from the source,
developers may end up using methods and attributes that are meant
for "internal use only".
o Code inspection can be hard, and very discouraging to novice
programmers trying to understand code written by gurus.
Interfaces try to solve these problems by providing a way for you to
describe how to use an object, and a mechanism for discovering that
description.
Creating Interfaces
The first step to creating a component, as you've been shown, is
to create an interface.
Interface objects can be conveniently constructed using the Python
'class' statement. Keep in mind that this syntax can be a little
misleading, because interfaces are *not* classes. It is important to
understand that using Python's class syntax is just a convenience, and
that the resulting object is an *interface*, not a class.
To create an interface object using Python's class syntax, create a
Python class that subclasses from 'Interface.Base'::
from Interface import Base
class Hello(Base):
def hello(self, name):
""" Say hello to the world """
This interface does not implement behavior for its methods, it
just describes an interface that a typical "Hello" object would
realize. By subclassing the 'Interface.Base' interface, the
resulting object 'Hello' is an interface object. The Python
interpreter confirms this::
>>> Hello
<Interface Hello at 812cbd4>
Now, you can associate the 'Hello' Interface with your new,
concrete class in which you define your user behavior. For
example::
class HelloComponent:
__implements__ = Hello
def hello(self, name):
return "Hello %s!" % name
This new class, 'HelloComponent' is a concrete class that
implements the 'Hello' interface. A class can realize more than one
interface. For example, say you had an interface called 'Item' that
described how an object worked as an item in a "Container" object. If
you wanted to assert that 'HelloComponent' instances realized the
'Item' interface as well as 'Hello', you can provide a sequence of
Interface objects to the 'HelloComponent' class::
class HelloComponent:
__implements__ = Hello, Item
This '__implements__' attribute is called an *interface assertion*. An
interface assertion can be either an interface, or a sequence of
interface assertions. Here's a more complex example::
class Sandwich:
__implements__ = (Food, (Nourishing, Delicious), (GetsStaleQuickly,
(EdibleWithHands, GoodForLunch)))
Interface assertions allow complex nesting of interfaces. This is
mostly useful when you wish to assert that your class implements
some specific interfaces, along with whatever interfaces your base
class implements::
class Sandwhich(Food):
__implements__ = (EdibleWithHands, GoodForLunch, Food.__implements__)
Take care before you assert that your class implements the
interfaces of your base classes.
The Interface Model
Interfaces can extend other interfaces. For example, let's extend the
'Hello' interface by adding an additional method::
class SmartHello(Hello):
""" A Hello object that remembers who it's greeted """
def lastGreeted(self):
""" Returns the name of the last person greeted. """
'SmartHello' extends the 'Hello' interface. It does this by using the
same syntax a class would use to subclass another class.
Now, you can ask the 'SmartHello' for a list of the interfaces it
extends with 'getBases'::
>>> SmartHello.getBases()
[<interface Hello at 80c72c8>]
An interface can extend any number of other interfaces, and
'getBases' will return that list of interfaces for you. If you
want to know if 'SmartHello' extends any other interface,
you could call 'getBases' and search through the list, but a
convenience method called 'extends' is provided that returns true
or false for this purpose::
>>> SmartHello.extends(Hello)
1
>>> SmartHello.extends(Sandwich)
0
>>>
Here you can see 'extends' can be used to determine if one interface
extends another.
You may notice a similarity between interfaces extending from
other interfaces and classes sub-classing from other classes.
This *is* a similar concept, but the two should not be considered
equal. There is no assumption that classes and interfaces exist
in a one to one relationship; one class may implement several
interfaces, and a class may not implement its base classes's
interfaces.
The distinction between a class and an interface should always be
kept clear. The purpose of a class is to share the implementation
of how an object works. The purpose of an interface is to
document how to work *with* an object, not how the object is
implemented. It is possible to have several different classes
with very different implementations realize the same interface.
Because of this, interfaces and classes should never be confused.
Querying an Interface
Interfaces can be queried for information.
The simplest case is to ask an interface the names of all
the various interface items it describes. From the Python interpreter,
for example, you can walk right up to an interface and ask it for its
*names*::
>>> User.names()
['getUserName', 'getFavoriteColor', 'getPassword']
Interfaces can also give you more interesting information about their
items. Interface objects can return a list of '(name, description)'
tuples about their items by calling the *namesAndDescriptions* method.
For example::
>>> User.namesAndDescriptions()
[('getUserName', <Interface.Method.Method instance at 80f38f0>),
('getFavoriteColor', <Interface.Method.Method instance at 80b24f0>),
('getPassword', <Interface.Method.Method instance at 80fded8>)]
As you can see, the "description" of the Interface's three items
in these cases are all 'Method' objects. Descriptions objects can
be either 'Attribute' or 'Method' objects. Attributes, methods,
and interface objects implement the following interface::
'getName()' -- Returns the name of the object.
'getDoc()' -- Returns the documentation for the object.
Method objects provide a way to describe rich meta-data about Python
methods. Method objects have the following methods:
'getSignatureInfo()' -- Returns a dictionary describing the
method parameters.
'getSignatureString()' -- Returns a human-readable string
representation of the method's signature.
For example::
>>> m=User.namesAndDescriptions()[0][1]
>>> m
<Interface.Method.Method instance at 80f38f0>
>>> m.getSignatureString()
'(fullName=1)'
>>> m.getSignatureInfo()
{'varargs': None, 'kwargs': None, 'optional': {'fullName': 1},
'required': (), 'positional': ('fullName',)}
You can use 'getSignatureInfo' to find out the names and types of
the method parameters.
Checking Implementation
You can ask an interface if a certain class or instance that you hand
it implements that interface. For example, say you want to know if
instances of the 'HelloComponent' class implement 'Hello'::
Hello.implementedByInstancesOf(HelloComponent)
This is a true expression. If you had an instance of
'HelloComponent', you can also ask the interface if that instance
implements the interface::
Hello.implementedBy(my_hello_instance)
This would also return true if *my_hello_instance* was an instance of
*HelloComponent*, or any other class that implemented the *Hello*
Interface.
Conclusion
Interfaces provide a simple way to describe your Python
objects. By using interfaces you document your objects'
capabilities. As Zope becomes more component oriented, your
objects will fit right in. While components and interfaces are
forward looking technologies, they are useful today for
documentation and verification.
|