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
|
# Copyright (c) 2007, Enthought, Inc.
# License: BSD Style.
# --(Adaptation)----------------------------------------------------------------
"""
Adaptation
==========
*Adaptation* is the process of transforming an object that does not implement
a specific interface, or set of interfaces, into one that does.
Defining Adapters
-----------------
In traits, an *adapter* is a special type of class whose role is to *transform*
some type of object which does not implement a specific interface, or set of
interfaces, into one that does.
Traits provides several different ways of writing adapters. We'll begin with
the simplest way, which is to create a subclass of **Adapter**.
Subclassing Adapter
-------------------
The **Adapter** base class is designed specifically for creating adapter
classes. It is actually a very simple class which streamlines the process of
creating new adapter classes by:
- Providing a standard constructor which normally does not need to be
overridden by subclasses.
- Only requiring use of the **adapts** function to define the adapter.
The standard constructor for the **Adapter** class has the form::
adapter_subclass_name(object_to_be_adapted)
where *adapter_subclass_name* is the name of the **Adapter** subclass.
The only thing the constructor does is::
self.adaptee = object_to_be_adapted
which assigns the object being adapted to the *adaptee* trait.
As an adapter writer, the only things you need to add to the subclass
definition are:
- An **adapts** function call declaring which interfaces the adapter class
implements on behalf of the object it is adapting.
The form of the **adapts** function is as follows::
adapts(client_class, interface [, interface2, ..., interfacen])
- A declaration for the *adaptee* trait (usually as an **Instance** of a
particular class).
- The actual implementations of the interfaces declared in the **adapts**
call. Usually the implementation code will be written in terms of the
adapter class's *adaptee* trait assigned by the class constructor.
The following shows the definition of a simple adapter class::
from traits.api import Adapter, Instance, implements
class PersonINameAdapter (Adapter):
# Declare what interfaces this adapter implements for its client:
adapts(Person, IName)
# Declare the type of client it supports:
adaptee = Instance(Person)
# Implement the 'IName' interface on behalf of its client:
def get_name (self):
return ('%s %s' % (self.adaptee.first_name, self.adaptee.last_name))
Rolling You Own Adapter Classes
-------------------------------
Note that using the **Adapter** base class is simply a convenience. It is not
necessary for an adapter class to derive from **Adapter**. However, if you do
not derive your adapter class from **Adapter**, then it is your responsibility
to provide all of the same information and setup implicitly provided by
**Adapter**.
In particular, in addition to using the *adapts* function to declare the set of
interfaces the class implements for its client object, you must also define the
constructor, or whatever means you define for binding the object to be adapted
to the adapter.
Creating an adapter class from scratch, we can re-write the previous adapter
example as follows::
from traits.api import HasTraits, Instance, adapts
class PersonINameAdapter(HasTraits):
# Declare what interfaces this adapter implements, and for who:
adapts(Person, IName)
# Declare the type of client it supports:
client = Instance(Person)
# Implement the adapter's constructor:
def __init__ (self, client):
self.client = client
# Implement the 'IName' interface on behalf of its client:
def get_name (self):
return ('%s %s' % (self.client.first_name, self.client.last_name))
As you can see, the main difference between this example and the last is:
- Explicit implementation of the adapter constructor.
Yet Another Way To Define Adapters
----------------------------------
It is also possible to declare a class to be an adapter class external to the
class definition itself, as shown in the following example::
class AnotherPersonAdapter (object):
# Implement the adapter's constructor:
def __init__ (self, person):
self.person = person
# Implement the 'IName' interface on behalf of its client:
def get_name (self):
return ('%s %s' % (self.person.first_name, self.person.last_name))
...
adapts(AnotherPersonAdapter, Person, IName)
When used in this way, the form of the **adapts** function is::
adapts(adapter_class, client_class, interface [, interface2, ...,
interfacen])
This form simply inserts the adapter class as the first argument (when
**adapts** is used inside of a class definition, this information is implicitly
available from the class itself).
Using Adapters
--------------
Now for the good part... how do you use adapters?
And the answer is... you don't. At least not explicitly.
In traits, adapters are created automatically whenever you assign an object to
an *interface* **AdaptsTo** or **AdaptedTo** trait and the object being
assigned does not implement the required interface. In this case, if an
adapter class exists that can adapt the specified object to the required
interface, an instance of the adapter class will be created for the object,
and the resulting adapter object is what ends up being assigned to the trait,
along with the original object. When using the **AdaptedTo** trait, the
adapter is assigned as the value of the trait, and the original object is
assigned as its *mapped* value. For the **AdaptsTo** trait, the original
object is assigned as the trait value, and the adapter is assigned as its
*mapped* value. In the case where the object does not require an adapter, the
object and adapted value are the same.
Note that it might happen that no adapter class exists that will adapt the
object to the required interface, but a pair, or series, of adapter classes
exist that will together adapt the object to the needed interface. In this
case, the required set of adapters will automatically be created for the
object and the final link in the chain adapter object (the one that actually
implements the required interface for some object class) will be used.
Whenever a situation like this arises, the adapted object used will always
contain the smallest set of available adapter objects needed to adapt the
original object.
The following code shows a simple example of using adaptation::
# Create a Person object (which does not implement the 'IName' interface):
william = Person(first_name = 'William', last_name = 'Adams')
# Create an apartment, and assign 'renter' the previous object. Since
# the value of 'renter' must implement 'IName', a 'PersonINameAdapter'
# object is automatically created and assigned:
apt = Apartment(renter = william)
# Verify that the resulting value implements 'IName' correctly:
print('Renter is: ', apt.renter.get_name())
# Check the type of object actually assigned to 'renter':
print(apt.renter)
Refer to the **Output** tab for the actual result of running this example.
Controlling Adaptation
----------------------
The **AdaptedTo** and **AdaptsTo** traits are actually subclasses of the
**Instance** trait. Normally, adaptation occurs automatically when values are
assigned to an **AdaptedTo** or **AdaptsTo** trait. However, any of the
**Instance**, **AdaptedTo** and **AdaptsTo** traits allow you to control how
adaptation is performed by means of the *adapt* metadata, which can have one of
the following values:
no
Adaptation is not allowed (This is the default for the **Instance** trait).
yes
Adaptation is allowed. If adaptation fails, an exception is raised (This is
the default for both the **AdaptedTo** and **AdaptsTo** traits).
default
Adapation is allowed. If adaptation fails, the default value for the trait
is assigned instead.
As an example of modifying the adaptation behavior of an **AdaptedTo** trait,
we could rewrite the example **Apartment** class as follows::
class Apartment(HasTraits):
renter = AdaptedTo(IName, adapt = 'no')
Using this definition, any value assigned to *renter* must itself implement
the **IName** interface, otherwise an exception is raised. Try modifying and
re-running the example code to verify that this is indeed the case.
"""
# --<Imports>-------------------------------------------------------------------
from __future__ import print_function
from traits.api import *
# --[IName Interface]-----------------------------------------------------------
# Define the 'IName' interface:
class IName(Interface):
def get_name(self):
""" Returns the name of an object. """
# --[Person Class]--------------------------------------------------------------
class Person(HasTraits):
first_name = Str("John")
last_name = Str("Doe")
# --[PersonINameAdapter Class]--------------------------------------------------
class PersonINameAdapter(Adapter):
# Declare what interfaces this adapter implements for its client:
adapts(Person, IName)
# Declare the type of client it supports:
adaptee = Instance(Person)
# Implementation of the 'IName' interface on behalf of its client:
def get_name(self):
""" Returns the name of an object. """
return "%s %s" % (self.adaptee.first_name, self.adaptee.last_name)
# --[Apartment Class]-----------------------------------------------------------
# Define a class using an object that implements the 'IName' interface:
class Apartment(HasTraits):
renter = AdaptedTo(IName)
# --[Example*]------------------------------------------------------------------
# Create an object implementing the 'IName' interface:
william = Person(first_name="William", last_name="Adams")
# Create an apartment, and assign 'renter' an object implementing 'IName':
apt = Apartment(renter=william)
# Verify that the object works correctly:
print("Renter is:", apt.renter.get_name())
# Check the type of object actually assigned to 'renter':
print(apt.renter)
|