a single class supporting multiple facets/interfaces

Beni Cherniavsky cben at techunix.technion.ac.il
Mon Jan 20 17:46:43 CET 2003


On 2003-01-20, David Garamond wrote:

> i want to have a class that can support multiple sets of methods, based
> on what the client requests. for example, see the following code:
>
> class C:
>    def foo(self):
>        print "foo"
>    def bar1(self):
>        print "bar version 1"
>    def bar2(self, arg):
>        print "bar version 2, arg =", arg
>    def facet(self, whichFacet=1):
>        if whichFacet == 1:
>            return C_facet1(self)
>        elif whichFacet == 2:
>            return C_facet2(self)
>        return
>
> class C_facet1:
>    def __init__(self, c):
>        self.foo = c.foo
>        self.bar = c.bar1
>
> class C_facet2:
>    def __init__(self, c):
>        self.foo = c.foo
>        self.bar = c.bar2
>
> c1 = C().facet(1) # client1 picks interface1
> c1.foo()        # prints "foo"
> c1.bar()        # prints "bar version 1"
>
> c2 = C().facet(2) # client2 picks interface2
> c2.foo()        # prints "foo"
> c2.bar(123)     # prints "bar version 2, arg = 123"
>
> if a client wants interface1, then she will get foo and bar (which is
> actually bar1). if he picks interface 2, then she will get foo and bar
> (which is actually bar2). for all she cares, she just wants to know/deal
> with a single [versatile] class, C. and from that single class, she can
> pick several sets of features/interfaces she needs/wants.
>
> the above code does what i want to accomplish, but i think it's very
> ugly. can anyone make it more elegant?
>
It's not perfect but here is my best:

class Facet:
    def __init__(self, obj):
        self._obj = obj
    def __getattr__(self, attr):
        return getattr(self._obj, attr)
    # ditto for setattr, delattr if needed

class C:
    def foo(self):
        print "foo"
    class facet1(Facet):
        def bar(self):
            print "bar version 1"
    facet1 = property(facet1)
    class facet2(Facet):
        def bar(self, arg):
            print "bar version 2, arg =", arg
    facet2 = property(facet2)

c1 = C().facet1   # client1 picks interface1
c1.foo()          # prints "foo"
c1.bar()          # prints "bar version 1"

c2 = C().facet2   # client2 picks interface2
c2.foo()          # prints "foo"
c2.bar(123)       # prints "bar version 2, arg = 123"

The first change was moving the facet classes into the `C` class.  This is
purely a matter of taste, YMMV.

The main difference is using a common proxy class (`Facet`) that
transparently inherits everything by default from the C instance.  Now you
only redefine the methods that are different and you do that directly in
the facet class.  You can still access attributes of the orignial object
in these methods thanks to Facet's magic.  That's much more readable but
is a bit slower.  If this really bothers you, I could try to turn `Facet`
into a metaclass that would implement all methods inside `C` (with name
mangling) while retaining this way of writing...

The property thing is optional - it looks a bit better to me.  If you
don't use it, you'll want to use somethign like your original `facet`
factory method, to avoid writing ``c = C(); c1 = c.facet1(c)``.  I was
pleasantly surprised that ``property(facet1)`` worked as is (the `fget`
function of a property, `facet1` in our case, is passed one argument - the
C object - that is exactly what `facet1.__init__` wants :-).

If you create the same facet for one object many times, you might
rather create one copy of each facet in `C.__init__()` (or do it
on-demand) and store it as an attribute of the C instance.

liking-that-``C.facet1.facet2.facet1.bar()``-works-ly y'rs
    Beni Cherniavsky <cben at tx.technion.ac.il>





More information about the Python-list mailing list