Delegation

Pytilities delegation utilities help in applying the concept of delegation to your code, it makes delegation less tedious so you are less tempted to use inheritance.

DelegationAspect

The most basic tool to help you is the DelegationAspect, it’s usage and use is best shown by example. Imagine you are making a game. You have a Player, an Enemy and a Box class. These all have health, can take damage and be destroyed. We decide to centralise this code in a Destructible class. As Player, Enemy and Box will have other characteristics as well we decide not to simply inherit from the Destructible but give ourselves the extra flexibility by giving each of them a Destructible instance and delegate calls on the public object to it.

Normally, you’d have to write:

class Player(object):
    def __init__(self):
        self._destructible = Destructible()

    def take_damage(self, amount):
        self._destructible.take_damage(amount)

    @getter
    def health(value):
        return self._destructible.health

    @health.setter
    def health(self, value):
        self._destructible.health = value

# repeat above code for Enemy and Box

With DelegationAspect you can narrow it down to:

from pytilities.delegation import DelegationAspect

class Player(object):
    def __init__(self):
        self._destructible = Destructible()

# make a new Aspect that will delegate any access to take_damage and health
# to the object it is applied to, to the _destructible attribute on that
# class
#
# first parameter: a descriptor that returns the object to delegate to
# second parameter: which attributes to delegate
delegation_aspect = DelegationAspect(AttributeDescriptor('_destructible'),
                                     ('take_damage', 'health'))
delegation_aspect.apply(Player)

# do the same for Enemy and Player, note you can reuse the aspect for all 3

Using DelegationAspect, the code is more maintainable, and still readable even though it’s shorter.

Note: For those wondering, AttributeDescriptor and various other special descriptors will be explained in the next part of the guide.

Delegation Profiles

Instead of specifying the attributes to delegate directly, you may want to use the in_profile and profile_carrier decorators to generate delegation Profile instances for you instead. A Profile is roughly equivalent to specifying a group of attributes (it’s actually far more powerful).

A lengthy example to explain the decorators:

from pytilities.delegation import profile_carrier, in_profile

# profile_carrier is required by in_profile decorators,
# it stores generated profiles in Destructible.attribute_profiles
# which is a dict of profile name to Profile instance
@profile_carrier
class Dispatcher(object):  # an event dispatcher (cfr observer pattern)
    # not included in any profile
    def __remove_handlers(self, event, owner):
        pass  # omitted actual code for clarity

    # not included in any profile
    def foo(self):
        pass  # omitted actual code for clarity

    # included in "public" profile
    @in_profile("public")
    def add_handler(self, event_name, handler, owner = None):
        pass  # omitted actual code for clarity

    # included in "default" profile
    @in_profile()
    def dispatch(self, event_name, *args, **keyword_args):
        pass  # omitted actual code for clarity

    @in_profile("public")
    def remove_handlers(self, event_name=None, owner=None):
        pass  # omitted actual code for clarity

    @in_profile("public")
    def remove_handler(self, event_name, handler, owner = None):
        pass  # omitted actual code for clarity

    @in_profile("public")
    def event(self, event_name, owner = None):
        pass  # omitted actual code for clarity

    @in_profile("public")
    def has_event(self, event_name):
        pass  # omitted actual code for clarity

    @in_profile("public")
    @property
    def events(self):
        pass  # omitted actual code for clarity

    @in_profile()
    def register_events(self, *event_names):
        pass  # omitted actual code for clarity

# advanced profile usage:
profiles = Dispatcher.attribute_profiles
# have default include all attributes of public
# if we didn't, default would only contain 'dispatch' and 'register_events'
profiles['default'] |= profiles['public']
# Profiles support most set operators

With all the above defined you could now make one of your classes look like an event dispatcher, e.g.:

from pytilities.delegation import DelegationAspect

class Player(object):
    def __init__(self):
        self._dispatcher = Dispatcher()

delegation_aspect = DelegationAspect(AttributeDescriptor('_destructible'),
                                     Dispatcher.attribute_profiles['public'])
delegation_aspect.apply(Player)

player = Player()
player.events  # this now returns all events that player might send out
player.dispatch('walked')  # this will give an attribute not found error as
                           # it's not included in the public profile

Advanced attribute mapping with Profile

Profiles also allow you to delegate to different named attributes, e.g.:

# in this example we'll try to map the top left coordinates of a rectangle to
# a vector (x,y coord)

class Rectangle(object):
    # has a Vector instance for its top left (and another for its bottom
    # right)

class Vector(object):
    # has x, y properties

profile = Profile()
profile.add_mappings('rwd', **{'left' : 'x', 'top' : 'y'})

delegation_aspect = DelegationAspect(AttributeDescriptor('top_left'), profile)
delegation_aspect.apply(Rectangle)

r = Rectangle()
r.top = 1  # this now sets its top_left.y to 1

Profile also allows you to map just read access, or write access, or delete access, e.g.:

# The following example is rather abstract;
# to be honest, I forgot why you might need this

class Delegate(object):
    @getter
    def x(self):
        print('delegated')

class Vector(object):
    def x(self):
        self.delegate = Delegate()
        self._x = 0

    @getter
    def x(self):
        print('original')
        return self._x

    @x.setter
    def x(self, value):
        print('original')
        self._x = value

profile = Profile()
# delegate only read access
profile.add_mappings('r', 'x')  # note you can also use in_profile(modifiers='r')

delegation_aspect = DelegationAspect(AttributeDescriptor('delegate'), profile)
delegation_aspect.apply(Vector)

v = Vector()
print(v.x)  # prints delegated
v.x = 1  # prints original

Table Of Contents

Previous topic

Aspect oriented programming tools

Next topic

Descriptors

This Page