Automatic Delegation for Dynamic Inheritance and Convenient Composition
'''
Comments are invited on a mixin class I wrote this weekend.
Thank you,
Raymond Hettinger
Dynamic Inheritance and Convenient Composition
PROBLEM
You are writing many empty delegation functions to attain the run-time
flexibility of composition. Coupling, code volume, and maintenance time
increase when new servant class methods added because each client needs
new delegation methods. The problem is common with design patterns like
Bridge, Decorator, State and Strategy.
SOLUTION
Uncle Guido provided getattr() and self.__dict__ which allow an
automatic
delegation mixin to be written in only 12 lines. Composition becomes as
convenient as inheritance while retaining its run-time flexibility.
CONSEQUENCES
For good or for ill, changes in servant classes immediately propogate
to their callers without writing additional code. Like inheritance,
convenience comes at a cost of weakened encapsulation.
'''
class AutoDelegate:
'Use as mixin class. Define "delegateVars" as a list of strings
with the delegation variables'
def __getattr__( self, key ):
if not self.__dict__.has_key( 'delegationVars' ): delegationVars
= []
for str in self.delegationVars: # if deleVars not defined, skip
try:
return getattr( self.__dict__[str], key )
except KeyError: # Continue if delegation
reference misspelled or not assigned
pass
except AttributeError: # Continue looking if this
delegate doesn't have the method
pass
raise AttributeError, key # Fail if the method can't be
found anywhere
''' EXAMPLE #1 '''
class Movie( AutoDelegate ): # installs the AutoDelegate
mixin
delegationVars = ['priceCode'] # tells AutoDelegate which
variables refer to objects with delegation code
def __init__( self, title ):
self.priceCode = RegularPrice()
self.title = title
def changeCode( self, aPriceObj ):
self.priceCode = aPriceObj
##def getPrice( self, qty ): # AutoDelegate eliminates the
need for this handwritten delegation method
## self.priceCode.getPrice(qty)
class RegularPrice:
def getPrice( self, qty ):
return 10*qty
class DiscountedPrice:
def getPrice( self, qty ):
return 10 + 9 * (qty-1)
a = Movie('Rambo')
b = Movie('Aliens')
b.changeCode( DiscountedPrice() )
print a.getPrice(10), b.getPrice(12) # these calls are
automatically forwarded to the composed object
''' EXAMPLE #2 '''
class Qparent:
qpar = 11
class Quantity( Qparent ):
qty = 4
def getQuan(self):
x = self.qty
self.qty = 6
return x
class Price:
def __init__( self, p ):
self.p = p
def getPrice( self ):
return self.p
class Movie( AutoDelegate ):
delegationVars = ['_ndef', '_price', '_q']
def __init__( self, charge ):
self._charge = charge
self._price = Price(5)
self._q = Quantity()
def getCharge( self ):
return self._charge
m = Movie(777)
print m.getPrice(), m.getCharge(), m.getQuan(), m.qty, m.qpar
try:
print m.notDef()
except:
print 'Intentional exception generated for underdefined method.'