[PEAK] Using attribute bindings within constructor when
using Component Factories
Phillip J. Eby
pje at telecommunity.com
Sun Jan 18 13:38:57 EST 2004
At 12:49 AM 1/18/04 -0600, Wayne Larsen wrote:
>Thanks for the response,
>
>The problem I had with assembly events, is that there does not seem to be
>any guarantee of order given an inheritance structure. So, for example:
>
>class TestService(binding.Component):
> protocols.advise(
> instancesProvide=[ITestService]
> )
>
> log = binding.Obtain('logger:TestService')
> message = binding.Obtain(PropertyName('helloworld.message'))
>
> def __init(self):
> self.log.info('from Parent __init: %s' % self.message)
>
> __init = binding.Make(__init, uponAssembly=1)
>
> def hello(self):
> self.log.info(self.message)
>
>
>class ChildTestService(TestService):
> def __init(self):
> self.log.info('from Child __init: %s' % self.message)
>
> __init = binding.Make(__init, uponAssembly=1)
There's a simple way to fix that.
class ChildTestService(TestService):
def __init(self):
self._TestService__init # don't call, just reference!
self.log.info('from Child __init: %s' % self.message)
What this does is access the inherited __init attribute. If it has already
run, then nothing happens. If it has not already run, it will be run. In
other words, we're replacing inheritance dependency with *data* dependency.
>The problem here is converting an existing application. The "normal" way
>of doing this is to put all intialization into your constructor. This
>initialization means setting themselves up based on data that I want to
>move into config files. If those classes depend on the base class
>initalizing first, I am unsure whether I can rely on assembly events. Is
>there any guarantee of order for assembly events?
No, none whatsoever. But if you access data that's needed, it will be
there. Think of it as a spreadsheet, where any cell (attribute) may refer
to (depend on) any other cell (attribute), and the needed cell (attribute)
will be calculated first.
So, if you're translating object that proactively set up attributes based
on input data, you need to rethink them in terms of how they provide the
output. Instead of:
class Foo:
def __init__(self,x,y):
self.x = x
self.y = y
self.z = x + y
self.q = x * y
think:
class Foo(binding.Component):
x = binding.Require("A number", adaptTo=int)
y = binding.Require("Another number", adaptTo=int)
z = binding.Make(lambda self: self.x+self.y)
q = binding.Make(lambda self: self.x*self.y)
Note also that this does *not* require assembly events. Assembly events
are actually a pretty specialized thing, intended for when you have
components that want to seek out other components they need to "register"
with. For example, 'AdaptiveTask' classes want to register with the
nearest ITaskQueue, so they have an assembly event to do that. If you are
using assembly events merely to initialize the object's data, you're
probably doing something wrong. Assembly events are for *behavior* that
needs to occur when you plug a component into a system.
If you're adapting existing application classes, note that you don't
necessarily have to make them into components. Suppose we wanted to wrap
the "legacy" Foo class above, that expects x and y to be supplied at
__init__ time. We could do:
class FooComponent(binding.Component):
x = binding.Require("A number", adaptTo=int)
y = binding.Require("Another number", adaptTo=int)
_foo = binding.Make(lambda self: Foo(self.x,self.y)
z = q = binding.Delegate('_foo')
So here, we delegate our 'z' and 'q' attributes to the '_foo' attribute,
which will automatically be created from our 'x' and 'y' attributes if/when
we need it. (Note: Delegate only delegates attribute *reads*, not writes.)
Anyway, as you can see, if you have code you want to reuse, you can simply
create replacement components like this, without having to fully translate
them to a data-driven design. Or, you can go all the way to a data-driven
design.
More information about the PEAK
mailing list