Home > python > Python: observer pattern per programmatori di coccio

Python: observer pattern per programmatori di coccio

17 Giugno 2013

Che cos’è?

Da wikipedia:

L’Observer pattern è un design pattern (potremmo tradurlo in schema progettuale)
utilizzato per tenere sotto controllo lo stato di diversi oggetti (Subject).
Lo schema UML (Unified Modeling Language) è il seguente:

+-------------------+
|      Observer     |<--------[ observer 1 ]
+-------------------+         [ observer 2 ]
|                   |         [ observer n ]
|   + __call__()    |
|                   |
+-------------------+
          |
          |
         / \
         \ /
+-------------------+
|      Subject      |
+-------------------+
|                   |
| + attach(observer)|
| + get()           |
| + set()           |
|                   |
+-------------------+

L’esempio seguente (tratto dal testo Python 3 Object Oriented Programming),
è piuttosto semplice:

class WareHouse:
    def __init__(self):
        self.observers = []
        self.product = None
        self.quantity = 0

    def attach(self, observer):
        self.observers.append(observer)

    def get_product(self):
        return self.product

    def set_product(self, value):
        self.product = value
        self.update_observers()

    def get_quantity(self):
        return self.quantity

    def set_quantity(self, value):
        self.quantity = value
        self.update_observers()

    def update_observers(self):
        for observer in self.observers:
            observer()

class Observer:
    def __init__(self, warehouse):
        self.warehouse = warehouse

    def __call__(self):
        print 'product: {}'.format(self.warehouse.product)
        print 'qty left: {}'.format(self.warehouse.quantity)

il funzionamento è il seguente:

>>> wh = WareHouse()

creaiamo un’istanza di WareHouse che, durante l’inizializzazione,
inizializza i valori delle variabili.
self.observers è una lista che conterrà tutte le istanze di Observer che creeremo.

>>> o = Observer(wh)

creiamo la prima istanza di Observer, che deve avere come parametro il soggetto
da ‘osservare’, cioè l’istanza di WareHouse ‘wh’.

>>> wh.attach(o)

fondamentale nel soggetto il metodo attach, che permette di inserire nella lista vuota,
l’observer appena creato.

>>> wh.set_product('keyboards')

product: keyboards
qty left: 0

Qui associamo alla variabile ‘product’ il valore ‘keyboards’, poichè in fase di inizializzazione
dell’istanza di WareHouse, product = None.
Chiamando il metodo set_product, oltre al settaggio della variabile, si ha una chiamata al
metodo update_observers(), il quale scorre la lista degli observers e li chiama ad uno ad uno.
Come avviene questo?
Grazie al metodo speciale __call__ di Observer, che lo rende callable (chiamabile)

for observer in self.observers:
            observer()
>>> wh.set_quantity(10)
product: keyboards
qty left: 10

come spiegato nel punto precedente.

Siccome siamo di coccio, ma vogliamo migliorare, possiamo rendere il codice più elegante usando
property al posto dei metodi get/set.
Condizione necessaria però, l’utilizzo di classi new-style (cioè che WhareHouse e Observer
derivino esplicitamente da object):

class WareHouse(object):
    def __init__(self):
        self.observers = []
        self._product = None # utilizzo _product per non entrare in conflitto
        self._quantity = 0   # con i nomi dei metodi

    def attach(self, observer):
        self.observers.append(observer)

    @property
    def product(self):
        return self._product

    @product.setter
    def product(self, value):
        self._product = value
        self._update_observers()

    @property
    def quantity(self):
        return self._quantity

    @quantity.setter
    def quantity(self, value):
        self._quantity = value
        self._update_observers()

    def _update_observers(self):
        for observer in self.observers:
            observer()

esempio di funzionamento:

>>> w = WareHouse()
>>> o = Observer(w)
>>> w.attach(o)
>>> w.product = 'joypads'
product: joypads
qty left: 0
>>> w.quantity = 10
product: joypads
qty left: 10
>>> w.quantity = 9
product: joypads
qty left: 9
>>> o2 = Observer(w)
>>> w.attach(o2)
>>> w.observers
[<__main__.Observer object at 0x011B3F10>, <__main__.Observer object at 0x011CCEF0>]
>>> w.quantity = 8
product: joypads # observer n.1
qty left: 8
product: joypads # observer n.2
qty left: 8

La cosa bella del pattern è che possiamo utilizzare anche più observer contemporaneamente,
ognuno con compiti differenti, scollegati tra loro.
La cosa importante è che tali observers lavorano completamente all’insaputa dell’oggetto monitorato.

al prossimo pattern…

Categorie:python Tag:
I commenti sono chiusi.