On 2005 Jan 15, at 01:02, Glyph Lefkowitz wrote: ...
Now, we have nowhere to hide PointPen's state on SegmentPen - and why
were we trying to in the first place?  It's a horrible breach of
encapsulation.  The whole *point* of adapters is to convert between
*different* interfaces, not merely to rename methods on the same
interface, or to add extra methods that work on the same data.  To me,

A common implementation technique, when you'd love to associate some extra data to an object, but can't rely on the object having a __dict__ to let you do that conveniently, is to have an auxiliary dict of bunches of extra data, keyed by object's id(). It's a bit messier, in that you have to deal with cleanup issues when the object goes away, as well as suffer an extra indirectness; but in many use cases it's quite workable. I don't see doing something like
myauxdict[id(obj)] = {'foo': 'bar'}
as "terribly invasive", and therefore neither do I see
obj.myauxfoo = 'bar'
as any more invasive -- just two implementation techniques for the same task with somewhat different tradeoffs. The task, associating extra data with obj without changing obj type's source, won't just go away.


Incidentally, the realization of this equivalence was a key step in my very early acceptance of Python. In the first few days, the concept "some external code might add an attribute to obj -- encapsulation breach!" made me wary; then CLICK, the first time I had to associate extra data to an object and realized the alleged ``breach'' was just a handy implementation help for the task I needed anyway, I started feeling much better about it.

Adapter use cases exist for all three structures:

1. the adapter just needs to change method names and signatures or combine existing methods of the object, no state additions;
2. the adapter needs to add some per-object state, which must be shared among different adapters which may simultaneously exist on the same object;
3. the adapter needs to add some per-adapter state, which must be distinct among different adapters which may simultaneously exist on the same object.


Case [1] is simplest because you don't have to wonder whether [2] or [3] are better, which may be why it's being thought of as "best". Case [3] may be dubious when we talk about AUTOMATIC adaptation, because in [3] making and using two separate adapters has very different semantics from making just one adapter and using it twice. When you build the adapter explicitly of course you have full control and hopefully awareness of that. For example, in Model/View, clearly you want multiple views on the same model and each view may well need a few presentation data of its own; if you think of it as adaptation, it's definitely a [3]. But do we really want _automatic_ adaptation -- passing a Model to a function which expects a View, and having some kind of default presentation data be used to make a default view on it? That, I guess, is the dubious part.


"different interfaces" means that the actual meaning of the operations
is different - sometimes subtly, sometimes dramatically.  There has to
be enough information in one interface to get started on the
implementation of another, but the fact that such information is
necessary doesn't mean it is sufficient.  It doesn't mean that there is
enough information in the original object to provide a complete
implementation of a different interface.

If there were enough information, why not just implement all of your
interfaces on the original class?  In the case of our hypothetical
cSegmentPen, we *already* have to modify the implementation of the
original class to satisfy the needs of a "stateless" adapter.  When
you're modifying cSegmentPen, why not just add the methods that you
wanted in the first place?

Reason #1: because the author of the cSegmentPen code cannot assume he or she knows about all the interfaces to which a cSegmentPen might be adapted a la [3]. If he or she provides a __dict__, or makes cSegmentPen weakly referenceable, all [3]-like adaptation needs are covered at one (heh heh) stroke.



Here's another example: I have a business logic class which lives in an
object database, typically used for a web application.  I convert this
into a desktop application.  Now, I want to adapt IBusinessThunk to
IGtkUIPlug.  In the process of doing so, I have to create a GTK widget,
loaded out of some sort of resource file, and put it on the screen.  I
have to register event handlers which are associated with that adapter.

OK, a typical case of model/view and thus a [3]. The issue is whether you want adaptation to be automatic or explicit, in such cases.


Most of the other use-cases I can think of are like the one James
mentions, where we really are using adaptation to shuffle around some
method names and provide simple glossing over totally isomorphic
functionality to provide backwards (or sideways, in the case of
almost-identical libraries provided on different platforms or
environments) compatibility.

And what's wrong with that? Those are the "case [1]" adapters, and they're very useful.


I guess this boils down to the issue that you don't think there are use cases for [2], where the extra state is needed but it had better be per-object, shared among adapters, and not per-adapter, distinct for each adapter.

Well, one example in the model/view area comes from 3d modeling for mechanical engineering: the model is a complex collection of solids which only deal with geometrical properties, the views are "cameras" rendering scenes onto windows on the screen. Each view has some modest state of its own (camera distance, angles, screen coordinates), but also there are some presentation data -- alien to the model itself, which only has geometry -- which are required to be shared among views, such as lighting information and surface texturing. One approach would be to first wrap the bare-model into an enriched-model, once only; and adapt only the enriched model to the views. If it's important to have different sets of views of the same geometry with different lighting &c, it's the only way to go; but sometimes the functional requirement is exactly the reverse -- ensure there is never any discrepancy among the lighting, texturing etc of the views over the same (geometrical) model. Nothing particularly wrong, then, in having the bunch of information that is the "enriched model" (lighting &c) be known only to the views but directly associated with the geometry-model.

For these reasons I would vastly prefer it if transitivity were declared
as a property of the *adaptation*, not of the adapter or the registry or
to be inferred from various vaguely-defined properties like
"losslessness" or "statelessness". I am also concerned about any
proposal which introduces transitivity-based errors at adaptation time
rather than at registration time, because by then it is definitely too
late to do anything about it.

Fair enough, but for Guido's suggested syntax of "def f(X:Y):..." meaning X=adapt(X,Y) at function entry, the issue is how that particular "default/implicit" adaptation should behave -- is it always allowed to be transitive, never, only when Y is an interface and not a class, or under what specific set of constraints?



Alex

_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to