Creating and Using Adapters, Components, and Protocols

Because the adaptation protocol is so simple and flexible, there are a few guidelines you should follow when using adapt() or creating __conform__ and __adapt__ methods, to ensure that adapted objects are as usable as unadapted objects.

First, adaptation should be idempotent. That is, if you adapt() an object to a protocol, and then adapt() the return value to the same protocol, the same object should be returned the second time. If you are using the protocols declaration API, it suffices to declare that instances of the adapter class provide the protocol they adapt to. That is, if an adapter class provides protocol P for objects of type X, then it should declare that it provides protocol P.

If you are not using the declaration API, but relying only upon your custom __conform__ and __adapt__ methods, you need to ensure that any adapters you return will return themselves when asked to support the protocol that they were returned as an adapter for.

Second, adaptation is not automatically symmetric. That is, if I have an object X that provides protocol P1, and I adapt() it to protocol P2, it is not guaranteed that I can adapt() the resulting object to P1 and receive the original object. Ideally, someone who defines an adapter function would also declare an inverse adapter function to ``unwrap'' an adapted object to its original identity. In practice, however, this can be complex, since the adapter might need some fairly global knowledge of the system to know when it is better to unwrap and rewrap, and when it is better to further wrap the existing wrapper.

Another issue that occurs with such wrapper-based adaptation, is that the wrapper does not have the same object identity as the base object, and may not hash or compare equal to it, either. Further, it is not guaranteed that subsequent calls to adapt() will yield the same wrapper object - in fact it's quite unlikely.

These characteristics of adapted objects can be easily dealt with, however, by following a few simple rules:

In some respects, these rules are similar to dealing with objects in statically typed languages like Java. In Java, if one simply has an ``object'', it is not possible to perform operations specific to an interface, without first ``casting'' the object to that interface. But, the object that was ``cast'' can't be stored in the same variable that the ``object'' was in, because it is of a different type.