Design Pattern: Subscrição
Subscrição é um padrão que é útil quando temos um objecto que tem uma variável ao qual queremos subscrever um certo serviço mas esta variável pode ser substituida e portanto a sua subscrição deve ser renovada.
Por exemplo, imaginando que temos um objecto que vai observar outro através da subscrição de um evento. Como foi dito antes vamos querer que essa subscrição acabe quando deixarmos de referenciá-lo:
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"
Esta é a implementação mais comum. Verificamos o valor antigo e caso exista cancelamos a subscrição. Após isso verificamos o valor recente, caso exista subscrevemos o nosso método de chamada posterior (callback).
O problema surge quando queremos subscrever mais do que um serviço, ou o mesmo tipo de serviço em mais de um local. A solução proposta é o padrão subscrição que utiliza a vida de um objecto para representar esse funcionamento.
Vamos reescrever o exemplo anterior:
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"
A subscrição da instância val (ValSubscription) funciona utilizando o construtor para registar a subscrição e o destrutor para cancelá-la. Depois temos de verificar se o valor é não nulo para podermos subscrevê-lo, ou em caso contrário para podermos sobrepor a subscrição antiga com o valor nulo - com isso removendo-a.
Para simplificar este último passo é aconselhada a criação de uma função que verifica o tal valor de entrada (neste caso val), no caso de ser nulo retorna nulo, caso contrário retorna o objecto de subscrição:
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"
Agora é possível reutilizar o mesmo padrão em vário locais na mesma classe ou em classes diferentes. A criação destas classes de subscrição são, na sua generalidade, bastante elementares e a implementação deverá ter em conta apenas problemas com referência circulares (ou triangulares).
No caso dos observadores, quando temos uma porção de código que é toda relacionada com a subscrição podemos movê-la para a classe de subscrição:
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)
Neste caso tivémos de mover a função de chamada posterior para fora da classe. Caso contrário teríamos uma referência circular (na classe ValSubscription) que a impediria da sua contagem de referências chegar a zero e do mesmo modo eleminando o seu intuito.