Skip to:

Tiago Cogumbreiro

O Irrepupável

Back to top

Subscripton Design Pattern

Subscription is a pattern useful when we have an object which contains a member variable referring to an object which we want to subscribe. That subscription is only valid when the reference exists.

For example, lets imagine you have an object which will observe another object through the subscription of an event. The subscription is only valid while you're holding its reference:

class Observer:
    _val = None
    def get_val(self):
        return self._val
    def set_val(self, val):
        if self._val is not None:
            self._val.unsubscribe(self.on_val_changed)
        self._val = val
        if val is not None:
            val.subscribe(self.on_val_changed)
    def on_val_changed(self):
        print "Value changed"

This is the usual implementation. The old value is verified and if it exists the old subscription is canceled. After that we check the new value and if it exists we subscribe to it using our callback method.

The problem is when you have to listen to more then one object in a class, or do this on alot of functions. The proposed solution is a Subscription which uses the life cycle of an object to represent the subscription of the target object.

Lets rewrite the last example:

class ValSubscription:
    def __init__(self, target, obj):
        target.subscribe(obj.on_val_changed)
        self.obj = obj
        self.target = target
    def __del__(self):
        self.target.unsubscribe(self.obj.on_val_changed)
class Observer:
    _val = None:
    def get_val(self):
        return self._val
    def set_val(self, val):
        self._val = val
        if val is not None:
            self._val_subsc = ValSubscription(val, self)
        else:
            self._val_subsc = None
    def on_val_changed(self):
        print "Value changed"

The subscription of the val instance (ValSubscription) works using the constructor to register the callback and the destructor to unregister it. After tis we verify if the value is null so that we can create a subscription, in the case that it is null we set the old variable to none - removing the old one (if there is one).

To simplify this last procedure it is advised that you create a new function to verify the target value (in this example val), in case of it being null return null, otherwise return the subscription object:

class ValSubscription:
    def __init__(self, target, obj):
        target.subscribe(obj.on_val_changed)
        self.obj = obj
        self.target = target
    def __del__(self):
        self.target.unsubscribe(self.obj.on_val_changed)
def subscribe_val(val, obj):
    if val is None:
        return None
    else:
        return ValSubscription(val, obj)
class Observer:
    _val = None:
    def get_val(self):
        return self._val
    def set_val(self, val):
        self._val = val
        self._val_subsc = subscribe_val(val, self)
    def on_val_changed(self):
        print "Value changed"

Now it is possible to reuse the same pattern in varius places of the same class or in different classes. The creation of these subscription classes is generally elementar but its implementation should be aware of circulear references (or triangular).

In the case of observers, when we have a part of the code that is related to the subscription you can move it to the subscription class:

def _on_val_changed():
        print "Value changed"
class ValSubscription:
    def __init__(self, target, obj):
        target.subscribe(_on_val_changed)
        self.obj = obj
        self.target = target
    def __del__(self):
        self.target.unsubscribe(self.obj.on_val_changed)
def subscribe_val(val, obj):
    if val is None:
        return None
    else:
        return ValSubscription(val, obj)
class Observer:
    _val = None:
    def get_val(self):
        return self._val
    def set_val(self, val):
        self._val = val
        self._val_subsc = subscribe_val(val, self)

In this case we had move the callback method to a function. Otherwise we would create a circular reference (in the class ValSubscription) which would stop the reference counting to reach zero therefore undermining its purpose.


Back to top