programmazione avanzata - unisa14/04/19 1 programmazione avanzata designpattern strutturali (i...

26
14/04/19 1 Programmazione Avanzata Design Pattern Strutturali (I parte) Programmazione Avanzata a.a. 2018-19 A. De Bonis Design Pattern strutturali I Design Pattern strutturali riguardano le relazioni tra entità quali classi e oggetti forniscono metodologie semplici per comporre oggetti per creare nuove funzionalità Programmazione Avanzata a.a. 2018-19 A. De Bonis

Upload: others

Post on 24-Jan-2020

12 views

Category:

Documents


0 download

TRANSCRIPT

14/04/19

1

Programmazione AvanzataDesign Pattern Strutturali (I parte)

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Design Pattern strutturali

• I Design Pattern strutturali riguardano le relazioni tra entità qualiclassi e oggetti• forniscono metodologie semplici per comporre oggetti per creare nuove

funzionalità

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

2

Adapter• L’Adapter è un design pattern strutturale che ci aiuta a rendere compatibili interfacce tra

di loro incompatibili

• In altre parole l’Adapter crea un livello che permette di comunicare a due interfaccedifferenti e che non sono in grado di comunicare tra di loro

• Esempio: Un sistema di e-commerce contiene una funzione calculate_total(order) in grado di calcolare l’ammontare di un ordine solo in Corone Danesi (DKK). • Vogliamo aggiungere il supporto per valute di uso più comune quali i Dollari USA (USD)

e gli Euro (EUR). • Se possediamo il codice sorgente del sistema possiamo estenderlo in modo da

incorporare nuove funzioni per effettuare le conversioni da DKK a EUR e USD. • Che accade però se non disponiamo del sorgente perchè l’applicazione ci è fornita da

una libreria esterna? In questo caso, possiamo usare la libreria ma non modificarla o estenderla.

• La soluzione fornita dall’Adapter consiste nel creare un livello extra (wrapper) cheeffettuala la conversione tra i formati delle valute.

• Esempio: Una funzione che si aspetta i parametri x, y e z ma abbiamo solo una funzionecon due parametri x e y. Possiamo usare l’adapter per “convertire” la funzione data in quella che ci serve

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Adapter: un semplice esempio • La nostra applicazione ha una classe Computer che mostra

l’informazione di base riguardo ad un computer.

class Computer: def __init__(self, name):

self.name = name def __str__(self):

return 'the {} computer'.format(self.name) def execute(self): return 'executes a program'

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

3

Adapter: un semplice esempio • Decidiamo di arricchire la nostra applicazione con altre funzionalità e per nostra

fortuna scopriamo due classi che potrebbero fare al nostro caso in due distintelibrerie: la classe Synthesizer e la classe Human

class Synthesizer: def __init__(self, name):

self.name = name def __str__(self): return 'the {} synthesizer'.format(self.name) def play(self): return 'is playing an electronic song’

class Human: def __init__(self, name): self.name = name def __str__(self):

return '{} the human'.format(self.name) def speak(self):

return 'says hello'Programmazione Avanzata a.a. 2018-19

A. De Bonis

Adapter: un semplice esempio • Poniamo le due classi in un modulo separato.• Problema: il client sa solo che può invocare il metodo execute() e non

ha alcuna idea dei metodi play() o speak(). • Come possiamo far funzionare il codice senza modificare le classi

Synthesizer e Human? • Creiamo una classe generica Adapter che ci permetta di unificare

oggetti di diverse interfacce in un’unica interfaccia

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

4

Adapter: un semplice esempio • L’argomento obj del metodo__init__() è l’oggetto che vogliamo

adattare

• adapted_methods è un dizionario che contiene le coppiechiave/valore dove la chiave è il metodo che il client invoca e il valore èil metodo che dovrebbe essere invocato.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class Adapter: def __init__(self, obj, adapted_methods):

self.obj = objself.__dict__.update(adapted_methods)

def __str__(self): return str(self.obj)

Adapter: un semplice esempio

def main(): objects = [Computer('Asus’)] synth = Synthesizer('moog’) objects.append(Adapter(synth, dict(execute=synth.play) )) human = Human('Bob’) objects.append(Adapter(human, dict(execute=human.speak))) for i in objects:

print('{} {}'.format(str(i), i.execute())) if __name__ == "__main__": main()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

objects è la lista di tutti gli oggetti. L’istanza di computer viene aggiunta alla lista senza adattamenti. Gli oggetti incompatibili (istanze di Human o Synthesizer) sono primaadattate usando la classe Adapter. Il client può usare execute() su tutti gli oggetti senza essere a conoscenza delle differenza tra le classi usate.

>>> python3 adapter.pythe Asus computer executes a programthe moog synthesizer is playing an electronic song Bob the human says hello

14/04/19

5

Adapter: un’implementazione che usa l’ereditarietà (1)class WhatIHave:

def g(self): pass

def h(self): pass

class WhatIWant:

def f(self): pass

class Adapter(WhatIWant):

def __init__(self, whatIHave):

self.whatIHave = whatIHave

def f(self):

# Implement behavior

#using methods in WhatIHave:

self.whatIHave.g()

self.whatIHave.h()

Programmazione Avanzata a.a. 2018-19

A. De Bonis

class WhatIUse:

def op(self, whatIWant):

whatIWant.f()

whatIUse = WhatIUse()whatIHave = WhatIHave()adapt= Adapter(whatIHave)whatIUse.op(adapt)

Adapter: un’implementazione che usa l’ereditarietà (1) – esempio Computer

class Human: ...

class Synthesizer: ...

class Computer: ...

class Adapter(Computer): def __init__(self, wih):

self.wih=wihdef execute(self):

if isinstance(self.wih, Synthesizer): return self.wih.play()

if isinstance(self.wih, Human):return self.wih.speak()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class WhatIUse: def op(self, comp):

return comp.execute()

whatIUse = WhatIUse()human = Human(‘Bob’)adapt= Adapter(human)print(whatIUse.op(adapt))

14/04/19

6

Adapter: un’altra implementazione che usa l’ereditarietà (2)class WhatIHave:

def g(self): pass

def h(self): pass

class WhatIWant:

def f(self): pass

class Adapter(WhatIWant):

def __init__(self, whatIHave):

self.whatIHave = whatIHave

def f(self):

# Implement behavior

#using methods in WhatIHave:

self.whatIHave.g()

self.whatIHave.h()

Programmazione Avanzata a.a. 2018-19

A. De Bonis

class WhatIUse:

def op(self, whatIWant):

whatIWant.f()

# build adapter use into op():

class WhatIUse2(WhatIUse):

def op(self, whatIHave):

Adapter(whatIhave).f()

whatIUse2 = WhatIUse2()

whatIHave = WhatIHave()

whatIUse2.op(whatIHave)

prime 4 classi stesse di

prima

Adapter: un’implementazione che usa l’ereditarietà (2) - esempio Computer

class Human: ...

class Synthesizer: ...

class Computer: ...

class Adapter(Computer): def __init__(self, wih):

self.wih=wihdef execute(self):

if isinstance(self.wih, Synthesizer): return self.wih.play()

if isinstance(self.wih, Human):return self.wih.speak()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class WhatIUse: def op(self, comp):

return comp.execute()

class WhatIUse2(WhatIUse):def op(self, wih):

return Adapter(wih).execute()

whatIUse2 = WhatIUse2()human = Human(’Bob’)print(whatIUse2.op(human))

14/04/19

7

Adapter: ancora un’altra implementazione che usa l’ereditarietà (3)class WhatIHave:

def g(self): pass def h(self): pass

class WhatIWant: def f(self): pass

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class WhatIUse: def op(self, whatIWant):

whatIWant.f()

# build adapter into WhatIHave:class WhatIHave2(WhatIHave, WhatIWant):

def f(self):self.g()self.h()

whatIUse = WhatIUse()whatIhave2=WhatIHave2()whatIUse.op(whatIHave2)

prime 3 classi stesse di prima

Adapter: un’implementazione che usa l’ereditarietà (3) - esempio Computer

class Human: ...

class Synthesizer: ...

class Computer: ...

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class WhatIUse: def op(self, comp):

return comp.execute()

class Human2(Human, Computer):def execute(self):

return self.speak()

class Synthesizer2(Synthesizer, Computer):def execute(self):

return self.play()

whatIUse = WhatIUse()human2 = Human2(’Bob’)print(whatIUse.op(human2))

14/04/19

8

Adapter: ancora un’altra implementazione che usa l’ereditarietà (4)class WhatIHave:

def g(self): pass def h(self): pass

class WhatIWant: def f(self): pass

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class WhatIUse: def op(self, whatIWant):

whatIWant.f()# inner adapter:class WhatIHave3(WhatIHave):

class InnerAdapter(WhatIWant):def __init__(self, outer):

self.outer = outerdef f(self):

self.outer.g()self.outer.h()

def whatIWant(self):return WhatIHave3.InnerAdapter(self)

whatIUse = WhatIUse()human=WhatIHave3()whatIUse.op(whatIHave3.whatIWant())

prime 3 classi stesse di prima

Adapter: un’implementazione che usa l’ereditarietà (4) - esempio Computer

class Human:

...

class Synthesizer:

...class Computer:

...

class WhatIUse:

def op(self, comp):

comp.execute()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class Human3(Human):

class InnerAdapter(Computer):def __init__(self, outer):

self.outer = outerdef execute(self):

return self.outer.speak()def whatIWant(self):

return Human3.InnerAdapter(self)

class Synthesizer3(Synthesizer):...

whatIUse = WhatIUse()human3=Human3('Bob')print(whatIUse.op(human3.whatIWant()))

14/04/19

9

Adapter: un’implementazione che usa l’ereditarietà (4 bis) - esempio Computer

class Human:

...

class Synthesizer:

...class Computer:

...

class WhatIUse:

def op(self, comp):

comp.execute()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

def createClass(cls):class hs(cls, Computer):

def execute(self):if isinstance(self,Human) :

return self.speak()else: return self.play()

return hs

whatIUse = WhatIUse()myclass=createClass(Human)human2 = myclass('Bob')print(whatIUse.op(human2))

Il pattern Decorator

• In generale, un decorator è una funzione che ha come unicoargomento una funzione e restituisce una funzione con lo stessonome della funzione originale ma con ulteriori funzionalità• In python c’è un supporto built-in per i decoratori. I decoratori

possono essere applicati alla funzioni e ai metodi.• Python permette anche di usare decoratori di classe, cioè funzioni che

hanno come unico argomento una classe e restituiscono una classecon lo stesso nome della classe originale ma con funzionalitàaggiuntive.• I decoratori di classe possono a volte essere utilizzati come

alternativa alla creazione di sottoclassi

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

10

Function Decorator

• Tutti i decoratori di funzioni o di metodi hanno la stessa struttura• Creazione della funzione wrapper:

• All’interno del wrapper invochiamo la funzione originale.

• Prima di invocare la funzione originale possiamo effettuare qualsiasi lavoro di preprocessing

• Dopo la chiamata siamo liberi di acquisire il risultato, di fare qualsiasi lavoro di postprocessing e di restituire qualsiasi valore vogliamo.

• Alla fine restituiamo la funzione wrapper come risultato del decoratore e questafunzione sostituisce la funzione originale acquisendo il suo nome.

• Applicazione di un decoratore:• Si scrive il simbolo @, allo stesso livello di indentazione dello statement def seguito

immediatamente dal nome del decoratore.

• è possibile applicare un decoratore ad una funzione decorata.

Programmazione Avanzata a.a. 2018-19

A. De Bonis

Function Decorator• La funzione mean() senza decoratore ha due o più argomenti

numerici e restituisce la loro media come un float. • Senza il decoratore la chiamata mean(5, "6", "7.5") genera un

TypeError perché non è possibile sommare int e str.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

11

Function Decorator• La funzione mean() decorata con il decoratore float_args_and_return

può accettare due o più argomenti di qualsiasi tipo che convertirà in un float. • Con la versione decorata, mean(5, "6", "7.5") non genera errore dal

momento che float("6") and float("7.5") producono numeri validi. .

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Function Decorator• Nel codice in basso, è stata creata la funzione senza il decoratore e

poi sostituita con il decoratore invocando il decoratore• A volte è necessario invocare i decoratori direttamente• vedremo in seguito degli esempi

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

12

Function Decorator• La funzione float_args_and_return() è un decoratore di funzione per cui ha

come argomento una singola funzione• Per convenzione, le funzioni wrapper hanno come argomenti un parametro

che indica un numero variabile di parametri (*args, nell’esempio) e un parametro di tipo keyword (**kwargs, nell’esempio)• Eventuali vincoli sugli argomenti sono gestiti dalla funzione originale. Nel

creare il decoratore, dobbiamo solo assicurarci che alla funzione originalevengano passati tutti gli argomenti.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Function Decorator• Per come è stato scritto il decoratore float_args_and_return,• la funzione decorata ha il valore dell’attributo __name__ settato a “wrapper”

invece che con il nome originale della funzione• non ha docstring anche nel caso in cui la funzione originale ha una docstring

• Per ovviare a questo inconveniente, la libreria standard di Python include il decoratore @functools.wraps che può essere usato per decorare una funzione wrapper dentro il decoratore e assicurare chegli attributi __name__ e __doc__ della funzione decorata contenganorispettivamente il nome e la docstring della funzione originale.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

13

Function Decorator• La versione in basso del decoratore float_args_and_return usa il

decoratore @functools.wraps per garantire che la funzione wrapper()• abbia il suo attributo __name__ correttamente settato con il nome della

funzione passata come argomento al decoratore (mean, nel nostro esempio)• abbia la docstring della funzione originale (vuota, nel nostro esempio)

• è sempre consigliabile usare il decoratore @functools.wraps dal momento che il suo uso ci assicura che• nei traceback vengano visualizzati i nomi corretti delle funzioni• si possa accedere alle docstring delle funzioni originali

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Decorator Factory

Programmazione Avanzata a.a. 2018-19 A. De Bonis

La funzione statically_typed() è una factory di decoratori, cioè una funzione che creadecoratori

14/04/19

14

La funzione zip()

Programmazione Avanzata a.a. 2018-19 A. De Bonis

zip(*iterables)• produce un iteratore che aggrega gli elementi da ciascuna delle sequenze iterabili in iterables;• restituisce un iteratore di tuple, dove la i-esima tupla contiene l’i-esimo elemento di ciascuna

sequenza in iterables.• l’iteratore restituito si ferma non appena arriva alla fine della più corta delle collezioni iterabili;• con una singola collezione iterabile restituisce un iteratore di tuple di un singolo elemento; con

nessun argomento restituisce un iteratore vuoto.

>>> x = [1, 2, 3]>>> y = [4, 5, 6] >>> zipped = zip(x, y) >>> list(zipped) [(1, 4), (2, 5), (3, 6)]>>> x2, y2 = zip(*zip(x, y)) >>> x == list(x2) and y == list(y2) True

Decorator Factory• La funzione statically_typed() non è essa stessa un decoratore perché non

prende come unico argomento una funzione, un metodo o una classe.• Nell’esempio della slide precedente c’è bisogno di parametrizzare il

decoratore dal momento che vogliamo specificare il tipo e il numero di parametri posizionali che possono essere accettati dalla funzione decorataed eventualmente specificare il tipo del suo valore di ritorno. Ciò varia dafunzione a funzione• In questo modo creiamo una funzione statically_typed() che prende i

parametri di cui abbiamo bisogno, un tipo per ogni argomento posizionalee un argomento keyword opzionale per specificare il tipo di ritorno• restituisce un decoratore

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

15

Decorator factory• Qui usiamo il decorator factory statically_typed() per decorare le

funzioni make_tagged() e repeat(). • Quando incontra @statically_typed(...) nel codice, Python invoca la

funzione con gli argomenti dati e poi usa la funzione restituita come decoratore per la funzione che segue (nel nostro esempio, make_tagged() o repeat()).

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Decorator factory

• La funzione wrapper() (creata quando la funzione decorator() è creatadalla funzione statically_typed()) ha catturato parte dello statocircostante, in particolare la tupla types e l’argomento keyword return_type.• Quando una funzione o un metodo catturano stati in questo modo si

parla di chiusura.• Il supporto di Python per le chiusure è ciò che rende possibile creare

funzioni factory parametrizzate, decoratori e factory di decoratori.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

16

Class Decorator• I decoratori di classe sono simili ai decoratori di funzioni ma sono

eseguiti al termine di uno statement class• I decoratori di classe possono essere usati sia per gestire le classi

dopo che esse sono state create sia per inserire un livello di logicaextra (wrapper) per gestire le istanze della classe quando sono create.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

def decorator(aClass): ...

@decorator class C: ...

def decorator(aClass): ...

class C: ...C = decorator(C)

è equivalente a

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

def count(aClass):aClass.numInstances = 0 return aClass

@countclass Spam: ... @countclass Sub(Spam): ... @countclass Other(Spam): ...

• è possible usare questo decoratore per dotare automaticamente le classi con un contatore di istanze.

• è possibile usare lo stesso approccio per aggiungere altri dati

14/04/19

17

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

@count #equivalente a spam=count(spam)def spam(): pass

@count #equivalente a Other=count(Other)class Other: pass

spam.numInstances #entrambi settati a 0 Other.numInstances

• Per come è stato definito, il decoratore count può essere applicato sia a classi che a funzioni

Proprietà• Per capire l’esempio proposto di class decorator occorre parlare degli

attributi property• La funzione built-in property permette di associare operazioni di fetch

e set ad attributi specifici• property(fget=None, fset=None, fdel=None, doc=None) restituisce un

attributo property• fget è una funzione per ottenere il valore di un attributo• fset è una funzione per settare un attributo• fdel è una funzione per cancellare un attributo • doc crea una docstring dell’atributo.

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

18

Proprietà• Se c è un’istanza di C, c.x =value invocherà il setter e del c.x invocherà il

deleter.• Se fornita, doc sarà la docstring dell’attributo property. In caso contrario,

viene copiata la docstring di fget (se esiste)

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class C: def __init__(self):

self._x = Nonedef getx(self): return self._xdef setx(self, value): self._x = valuedef delx(self): del self._x

x = property(getx, setx, delx, "I'm the 'x' property.")

Proprietà• Il decoratore @property trasforma il metodo voltage() in un “getter” per

l’attributo read-only voltage e setta la docstring per voltage a “Get the current voltage.”• equivalente a property(voltage, “Get the current voltage”)

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class Parrot: def __init__(self):

self._voltage = 100000

@propertydef voltage(self):

"""Get the current voltage."""return self._voltage

14/04/19

19

Proprietà• Un oggetto property ha i metodi getter, setter e deleter che possono essere usati come

decoratori per creare una copia della proprietà con la corrispondente funzione accessoriauguale alla funzione decorata• Questi due codici sono equivalenti

• nel codice a sinistra dobbiamo stare attenti a dare alla funzioni aggiuntive lo stesso nome dellaproprietà originale (n, nel nostro esempio).

Programmazione Avanzata a.a. 2018-19 A. De Bonis

class C: def __init__(self):

self._x = None@propertydef x(self):

"""I'm the 'x' property."""return self._x

@x.setterdef x(self, value):

self._x = value @x.deleterdef x(self):

del self._x

class C: def __init__(self):

self._x = Nonedef getx(self): return self._xdef setx(self, value): self._x = valuedef delx(self): del self._x

x = property(getx, setx, delx, "I'm the 'x' property.")

Class Decorator• È abbastanza comune creare classi che hanno molte proprietà read-

write. Tali classi hanno molto codice duplicato o parzialmenteduplicato per i getter e setter.• Esempio: Una classe Book che mantiene il titolo del libro, lo ISBN, il

prezzo, e la quantità. Vorremmo• quattro decoratori @property, tutti fondamentalmente con lo stesso codice

(ad esempio, @property def title(self): return title). • quattro metodi setter il cui codice differirebbe solo in parte

• I decoratori di classe consentono di evitare la duplicazione del codice

Programmazione Avanzata a.a. 2018-19 A. De Bonis

14/04/19

20

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

self.title, self.isbn, self.price, self.quantity sonoproprietà per cui gli assegnamenti cheavvengono in __init__() sono tutti effettuati daisetter delle proprietàInvece di scrivere il codice per creare le proprietàcon i loro getter e setter, si usa un decoratore di classeLa funzione ensure() è un decorator factory: accetta due parametri, il nome di una proprietà euna funzione di validazione, e restituisce un decoratore di classe

Nel codice applico 4 volte @ensure per creare le 4 proprietà in questo ordine: quantity, price, isbn, title

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Lo statement class Book deve essere eseguito per primo perché la classe Book servecome parametro di ensure(“quantity”,...)• la classe ottenuta applicando il decoratore restituito da ensure(“quantity”,...) serve

come parametro in ensure(“price”,...)e così via.

14/04/19

21

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

decorator() • riceve una classe come unico

argomento e crea un nome privato e lo assegna privateName;

• crea una funzione getter cherestituisce il valore associato allaproperty;

• crea una funzione setter che, nel casoin cui validate() non lanciun’eccezione, modifica il valore dellaproperty con il nuovo valore value, eventualmente creando l’attributoproperty se non esiste

• La funzione ensure() crea un decoratore di classe parametrizzato dal nome dellaproprietà, dalla funzione di validazione e da una docstring opzionale.

• Ogni volta che un decoratore di classe restituito da ensure() è usato per una particolareclasse, quella classe viene dotata della proprietà il cui nome è specificato dal primo parametro di ensure()

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Una volta che sono stati creati getter e setter, essi vengono usati per creare una nuova proprietà cheviene aggiunta come attributo alla classe passata come argomento a decorator().

• La proprietà viene creata invocando property() nell’istruzione evidenziata:• in questa istruzione viene invocata la funzione built-in setattr() per associare la proprietà alla

classe• La proprietà così creata avrà nella classe il nome pubblico corrispondente al parametro name di

ensure()

14/04/19

22

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Qualche considerazione sulle funzioni di validazione:• la funzione di validazione is_in_range() usata per price e per quantity è una factory

function che restituisce una nuova funzione is_in_range() che ha i valori minimo emassimo codificati al suo interno

• AssertionError se nessuno tra minimum o maximum è diverso da None

• ValueError se value non è un numero, se minimum è diverso da None e value <minimum, oppure se maximum è diverso da None e value>maximum

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Questa funzione di validazione è usata per la proprietà title e ci assicurache il titolo non sia una stringa vuota. • Il nome di una proprietà è utile nei messaggi di errore: nell’esempio viene sollevata

l’eccezione ValueError se name non è una stringa o se è una stringa vuota e ilnome della proprietà compare nel messaggio di errore.

14/04/19

23

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Applicare molti decoratori in sequenza è una pratica che non èaccettata da tutti i programmatori

• In questo esempio, le 4 proprietàvengono create come istanze dellaclasse Ensure

• __init__ della classe Book associa le proprietà all’istanza di Book creata

• il decoratore di classe @do_ensurerimpiazza ciascuna delle 4 istanze di Ensure con una proprietà con lo stesso nome dell’istanza. La proprietà avrà come funzione di validazione quella passata ad Ensure()

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• La classe Ensure è usata per memorizzare• la funzione di validazione che sarà usata dal setter della proprietà• l’eventuale docstring della proprietà

• Ad esempio, l’attributo title di Book è inizialmente creato come un’istanza di Ensure ma dopo la creazione della classe Book ildecoratore @do_ensure rimpiazza ogni istanza di Ensure con unaproprietà. Il setter usa la funzione di validazione con cui l’istanza è stacreata.

14/04/19

24

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Il decoratore di classe do_ensure consiste di tre parti:• La prima parte definisce la funzione innestata make_property(). La funzione

make_property() prende come parametro name (ad esempio, title) e un attributo di tipo Ensure e crea una proprietà il cui valore viene memorizzato inun attributo privato (ad esempio, “_title”). Il setter al suo interno invoca la funzione di validazione.

Class Decorator

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• La seconda parte itera sugli attributi della classe e rimpiazza ciascun attributodi tipo Ensure con una nuova proprietà con lo stesso nome dell’attributorimpiazzato.• La terza parte restituisce la classe modificata

14/04/19

25

Class Decorator nelle derivazioni di classi

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• A volte creiamo una classe di base con metodi o dati al solo scopo di poterla derivare più volte.• Ciò evita di dover duplicare i metodi o i dati nelle sottoclassi ma se i

metodi ereditati o i dati non vengono mai modificati nelle sottoclassi, èpossibile usare un decoratore di classe per raggiungere lo stessoobiettivo.

Class Decorator nelle derivazioni di classi

Programmazione Avanzata a.a. 2018-19 A. De Bonis

• Questa è la classe base che verrà estesa da classi che non modificano ilmetodo ereditato on_change() method.

14/04/19

26

Class Decorator nelle derivazioni di classi

Programmazione Avanzata a.a. 2018-19 A. De Bonis

Possiamo applicare il decoratore di classe definito in basso in questo modo:@mediated class Button: ... La classe Button avrà esattamente lo stesso comportamento che avrebbe avuto se l’avessimo definita come sottoclasse di Mediated con class Button(Mediated): ...