[TransWarp] New interfaces
Phillip J. Eby
pje at telecommunity.com
Wed May 14 13:12:46 EDT 2003
At 06:33 PM 5/14/03 +0400, Oleg Broytmann wrote:
>Hello.
>
> I have upgraded to the latest PEAK (from CVS) and started to redebug my
>programs. I changed __implements__ to implements(), exchanged parameters
>for findUtility and so on. One thing that I cannot discover is how to get
>back information that was in __implements__. In previous version I did
>this:
>
> if _factory.__dict__.has_key("__implements__"):
> _provides = _factory.__implements__
>
> How can I do this now?
You don't. We're specifically deprecating interface introspection. Use
adaptation instead. In other words, every place in PEAK where we used to
do this:
if IFoo.isImplementedBy(ob):
spamFoo(ob)
elif IBar.isImplementedBy(ob):
spamBar(ob)
...
We now do this:
adapt(ob,ISpam).spam()
and separately declare adapters from IFoo to ISpam and IBar to ISpam. The
other thing we do, (but it's bad style and I intend to fix the remaining
uses), is to check the result of an adapt(), like this:
if adapt(ob,ISpam,None) is ob:
This is equivalent to the old 'ISpam.isImplementedBy(ob)'. But it's bad
usage of interfaces, because interfaces document *behavior*, not
metadata. If you need metadata, you should define an interface for
accessing the metadata, adapt to that interface, and then query the
metadata. I plan to make that change in those few areas of PEAK that still
introspect interfaces in this way.
Why is introspection of interfaces bad? Because it inherently closes code
to extensibility. Code that introspects is limited to working with
interfaces it already knows about. Only the code's author can extend the
code to handle new kinds of objects. With adaptation, a third party can
define an adapter between a component supplied by one framework, and an
interface required by another framework. Better yet, a fourth party can
use the third party's adapter without having to know about it, as long as
the adapter has been installed.
If you have code that accesses __implements__ in order to examine an
object's interfaces as metadata (e.g. for printing out help or
documentation, to generate other code, etc.), you should define an
interface for accessing such metadata, and then use adaptation to get at
that interface. The protocols package includes interfaces called
'IOpenImplementor' and 'IOpenProvider' that you can have your classes
implement, in order to have them receive notification when certain
interface adaptation information is registered about them. You can
subclass the existing PEAK metaclasses that implement that interface (e.g.
binding.Activator and binding.ActiveClass).
But, I suggest you consider carefully whether you really need to introspect
interfaces, or whether you just need to adapt to them. In general,
'adapt()' makes it trivially easy to direct behavior, without needing to
introspect. So far in every case where I've looked at such introspective
code in PEAK, it has become simpler, cleaner, and much more extensible when
I refactored it to use adaptation. For example, it's now possible for you
to define your own kinds of keys that can be looked up by the
'lookupComponent()' method of the binding.IComponent interface. That
wasn't possible before, because it did isinstance() and isImplementedBy()
checks before to decide what to do.
By the way, there is going to be one more interface API change; sometime in
the next day or two we will be refactoring the protocol declaration API to
have four functions that you'll normally use to do all protocol and adapter
declaration. 'implements()' and certain others will still be available
from 'protocols.zope' if you prefer to use the Zope X3 declaration
syntax. But the four primary API calls will be:
protocols.advise() - declare protocol information about the class or module
protocols.adviseObject() - declare protocol information about the object
specified
protocols.declareImplementation() - declare information about a specified
class or type
protocols.declareAdapter() - declare information about an adapter class or
function
These calls will all accept keyword arguments, e.g.:
class Foo(Component):
protocols.advise(
instancesProvide = [IFoo, IBar],
classProvides = [IFooFactory]
)
and:
protocols.declareAdapter(
FooBarsAsSpam,
provides = [ISpam],
asAdapterForProtocols = [IFoo,IBar]
)
(Note that the only "magic" API will be 'protocols.advise'; the other
functions will all take an explicit first argument in conjunction with the
keywords.)
'adapt()' and 'protocols' will be available from peak.api, the way
'binding' and such are available now, so if you are using 'from peak.api
import *' it will not necessary to change your code to get access to
them. 'protocols.Interface' and 'protocols.Attribute' will thus be
available, as an alternative to importing them from 'peak.interface'.
To help minimize transition impact, 'peak.interface' will remain in place
through 0.5a2, but will be removed when we start work on alpha 3.
More information about the PEAK
mailing list