Design Pattern: Facade - Programmazione Avanzata
←
→
Page content transcription
If your browser does not render page correctly, please read the page content below
02/12/20 Programmazione Avanzata Design Pattern: Facade Programmazione Avanzata a.a. 2020-21 A. De Bonis 45 Il Design Pattern Facade • Il design pattern Facade è un design pattern strutturale che fornisce un’interfaccia semplificata per un sistema costituito da interfacce o classi troppo complesse o troppo di basso livello. • Esempio: La libreria standard di Python fornisce moduli per gestire file compressi gzip, tarballs e zip. Questi moduli hanno interfacce diverse. • Immaginiamo di voler accedere ai nomi di un file di archivio ed estrarre i suoi file usando un’interfaccia semplice. • Soluzione: Usiamo il design pattern Facade per fornire un’interfaccia semplice e uniforme che delega la maggior parte del vero lavoro alla libreria standard. Programmazione Avanzata a.a. 2020-21 A. De Bonis 46 1
02/12/20 Il Design Pattern Facade: un esempio 60 Chapter 2. Structural Design Patterns in Python Archive filename names() unpack() 60 Chapter 2. Structural Design Patterns in Python gzip tarfile.TarFile zipfile.ZipFile open() getnames() Archive namelist() extractall() filename extractall() names() Figure 2.7 The Archive façade unpack() Programmazione Avanzata a.a. 2020-21 A. De Bonis only when asked for the archive’s names or to unpack the archive will it actually 47 open the archive file. (The code quoted in this section is from Unpack.py.) gzip tarfile.TarFile zipfile.ZipFile class Archive: open() getnames() namelist() extractall() extractall() def __init__(self, filename): self._names = None Figure 2.7 The Archive façade Il Design Pattern Facade: un esempio self._unpack = None only when asked for=the self._file archive’s names or to unpack the archive will it actually None open the archive file. (The code quoted in this section is from Unpack.py.) self.filename = filename class Archive: The self._names variable is expected to hold a callable that will return a list def __init__(self, of the archive’s names. filename): Similarly, the self._unpack variable is for holding a self._names = None callable that will extract all the archive’s files into the current directory. The self._unpack = None ptg11 is for holding self._file self._file a file object that has been opened on the archive. And = None self.filename is a read-write property holding the archive file’s filename. self.filename = filename The self._names variable is expected to hold a callable that will return a list @property • La variabile of theself._names archive’s ènames. usata perSimilarly, contenere un callable the che restituisce self._unpack una lista variable is dei fornomi dell’archivio. holding a def filename(self): callable that will return extract all the archive’s files into the current directory. The self.__filename • La variabile the self._file self._unpack è usata is for holding per mantere a file un callable object that has beenche estrae opened tutti oni flie thedell’archivio archive. And nella directory corrente. self.filename is a read-write property holding the archive file’s filename. @filename.setter • La variabile self._file def@property è usata per mantenere filename(self, name): il file object che è stato aperto per accedere all’archivio. defself.close() filename(self): • self.filename è una proprietà che mantiene il nome del file di archivio. return self.__filename self.__filename = name Programmazione Avanzata a.a. 2020-21 A. De Bonis @filename.setter If the user changes the filename (e.g., using archive.filename = newname), the 48 def filename(self, name): current archive file is closed (if it is open). We do not immediately open the new self.close() archive, though, since the= Archive self.__filename name class uses lazy evaluation and so only opens the archive when necessary. If the user changes the filename (e.g., using archive.filename = newname), the current archive file is closed (if it is open). We do not immediately open the new 2 archive, though, since the Archive class uses lazy evaluation and so only opens the archive when necessary. www.it-ebooks.info
open the archive file. (The code quoted in this section is from Unpack.py.) class Archive: def __init__(self, filename): self._names = None 02/12/20 self._unpack = None p self._file = None self.filename = filename The self._names variable is expected to hold a callable that will return a list of the archive’s names. Similarly, the self._unpack variable is for holding a callable that will extract all the archive’s files into the current directory. The Il Design Pattern Facade: un esempio self._file is for holding a file object that has been opened on the archive. And self.filename is a read-write property holding the archive file’s filename. @property def filename(self): return self.__filename @filename.setter def filename(self, name): self.close() self.__filename = name If the user changes the filename (e.g., using archive.filename = newname), the Se l’utente cambiaarchive current il filename, adclosed file is esempio(if archive.filename it is open). We do = newname, allora il open not immediately file d’archivio the new corrente,archive, se aperto, viene chiuso though, since ethe viene aggiornata Archive la variabile class uses __filename.and so only opens lazy evaluation Non vienetheimmediatamente aperto il nuovo archivio, in quanto la classe Archive apre l’archivio solo archive when necessary. se necessario. www.it-ebooks.info Programmazione Avanzata a.a. 2020-21 A. De Bonis 49 Il Design Pattern Facade: un esempio 2.5. Façade Pattern 2.5. Façade Pattern 61 61 def close(self): def close(self): if self._file is not None: if self._fileself._file.close() is not None: self._names = self._unpack = self._file = None self._file.close() self._names = self._unpack = self._file = None In theory, users of the Archive class are expected to call the close() method when theyusers In theory, have finished ofinvocano with an the Archive instance. class The method are expected to call closes the filemethod object (if one is Gli utenti della classe Archive close() quando hanno finito contheun’istanza. close() open) and sets the self._names, self._unpack, and self._file variables to None to when they have Il metodo chiude il filefinished objectthem.with , se anfile c’è un instance. The method object aperto, e setta closes the file self._names, object (if one self._unpack, e is invalidate open) and sets self._file a None per invalidarli.the self._names , self._unpack , and self._file variables to None to We have invalidate made the Archive class a context manager (as we will see in a moment) them. La classe Archive so, è unin context practice, users don’t manager need eclass così in to callgli pratica utentithemselves, close() non providing they use the We have class made in a the withArchive statement. a context For example:manager (ashanno bisogno we will see indiachiamare moment) close(), a patto so, inche usino lausers practice, classedon’t in uno statement need with, come to call close() nel codiceproviding themselves, qui in basso: they use the class in a with with statement. For example: Archive(zipFilename) as archive: print(archive.names()) with Archive(zipFilename) archive.unpack() as archive: print(archive.names()) Programmazione Avanzata a.a. 2020-21 Here we create an Archive for a zip archive.unpack() A. Defile, Bonis print its names to the console, and then extract all its files in the current directory. And because the archive is a context 50 manager, Here we an Archive for aiszip createarchive.close() called file, automatically print its names when theconsole, to the goesthen archive and out of the with statement’s scope. extract all its files in the current directory. And because the archive is a context manager, archive.close() is called automatically when the archive goes out of def __enter__(self): the with statement’s scope. return self def __enter__(self): 3 def __exit__(self, exc_type, exc_value, traceback): returnself.close() self def __exit__(self, These two methodsexc_type, exc_value, are sufficient to maketraceback): an Archive into a context manager.
open) and sets the self._names, self._unpack, and self._file variables to None to invalidate them. We have made the Archive class a context manager (as we will see in a moment) 2.5. Façade so, inPattern 61the practice, users don’t need to call close() themselves, providing they use class in a with statement. For example: 02/12/20 def close(self): with Archive(zipFilename) as archive: if self._file is not None: print(archive.names()) self._file.close() archive.unpack() self._names = self._unpack = self._file = None Here we create an Archive for a zip file, print its names to the console, and then In theory, usersall extract of its thefiles in theclass Archive are directory. current expected to call And the close() because method the archive is awhen context Il Design Pattern Facade: un esempio they have open) and finished manager, sets the with an instance. archive.close() self._names the with statement’s scope. , is The called self._unpack method , and closes automatically the when self._file file the object archive variables (if to one goes None is of out to invalidate them. def __enter__(self): We have made the Archive class a context manager (as we will see in a moment) return self so, in practice, users don’t need to call close() themselves, providing they use the class in a with defstatement. For example: __exit__(self, exc_type, exc_value, traceback): self.close() with Archive(zipFilename) as archive: print(archive.names()) These two methods are sufficient to make an Archive into a context manager. • Questi due Themetodi rendonomethod __enter__() archive.unpack() un Archivio un context returns manager self (an Archive instance), which is assigned to • Il metodo __enter__() method restituisce self (un’istanza the with … as statement’s variable. The __exit__() di Archive) che viene method assegnata closes alla the archive’s variabilefile dello statement object with ...as Here we create an(ifArchive one is open), andfile, for a zip since it (implicitly) print its names returns None, any to the console, exceptions and then • Il metodo __exit__() that have chiude il file occurred will object be dell’archivionormally. propagated se c’è ne uno aperto.. extract all its files in the current directory. And because the archive is a context manager, archive.close() is called automatically when the archive goes out of def names(self): the with statement’s scope. if self._file is None: self._prepare() def __enter__(self): return self._names() Programmazione Avanzata a.a. 2020-21 return self A. De Bonis 51 This method returns a list of the archive’s filenames, opening the archive and def setting __exit__(self, self._names exc_type, exc_value, and self._unpack traceback):callables (using self._pre- to appropriate self.close() pare()) if it isn’t open already. These two methods are sufficient to make an Archive into a context manager. The __enter__() method returns self (an Archive instance), which is assigned to the with … as statement’s variable. The __exit__() method closes the archive’s Il Design Pattern Facade: un esempio file object (if one is open), and since www.it-ebooks.info it (implicitly) returns None, any exceptions that have occurred will be propagated normally. def names(self): if self._file is None: self._prepare() return self._names() This method returns a list of the archive’s filenames, opening the archive and Questo settingmetodo restituisce self._names una lista deito and self._unpack nomi dei file dell’archivio appropriate aprendo callables (using l’archivio self._pre- pare()) if it isn’t open already. (se non è già aperto) e ponendo in self._names e in self._unpack i callable appropriati utilizzando il metodo self.prepare(). www.it-ebooks.info Programmazione Avanzata a.a. 2020-21 A. De Bonis 52 4
02/12/20 Il62Design Pattern Facade: Chapter 2.un esempio Structural Design Patterns in Python def unpack(self): if self._file is None: self._prepare() self._unpack() This method unpacks all the archive’s files, but as we will see, only if all of their Questo metodo spacchetta tutti i file di archivio ma solo se tutti i loro nomi sono names are “safe”. “safe”. def _prepare(self): if self.filename.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".zip")): self._prepare_tarball_or_zip() elif self.filename.endswith(".gz"): self._prepare_gzip() Programmazione Avanzata a.a. 2020-21 A. De Bonis else: 53 62 raise ValueError("unreadable: Chapter 2. Structural{}".format(self.filename)) Design Patterns in Python This def method delegates the preparation to suitable methods. For tarballs and unpack(self): zip files if theself._file necessaryis None: code is very similar, so they are prepared in the same self._prepare() method. self._unpack() But gzipped files are handled differently and so have their own sepa- rate method. IlThe Design preparation names Pattern are “safe”. methods must Facade: un esempio This method unpacks all the archive’s files, but as we will see, only if all of their assign callables to the self._names and self._un- pack variables, so that these can be called in the names() and unpack() methods def _prepare(self): we have just seen. if self.filename.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".zip")): def _prepare_tarball_or_zip(self): self._prepare_tarball_or_zip() def self.filename.endswith(".gz"): elif safe_extractall(): unsafe = [] self._prepare_gzip() else: for name in self.names(): raise ValueError("unreadable: {}".format(self.filename)) if not self.is_safe(name): This method delegates the unsafe.append(name) preparation to suitable methods. For tarballs and Questozip metodo files thedelega ifla preparazione code is ai unsafe: necessary metodi very adatti similar, soathey occuparsene, are prepared in the same Per i tarball method. e i file Butzipgzipped il codice necessario files raise are è moltodifferently handled ValueError("unsafesimile e per and toquesto essi their so have unpack: vengono ownpreparati sepa- dallo {}".format(unsafe)) stesso rate metodo. method. self._file.extractall() ptg11 I file gzip The richiedono preparation unamethods gestione must diversa e per callables assign questo hanno to theunself._names metodo a parte. and self._un- if self.filename.endswith(".zip"): I metodi di preparazione devono assegnare dei callable pack variables, so that these can be called in the names() and alle variabili self._names unpack()emethods self._unpack in modo che queste self._file possano essere = zipfile.ZipFile(self.filename) chimate nei metodi names() e unpack(). we have just seen. self._names = self._file.namelist Programmazione Avanzata a.a. 2020-21 self._unpack = safe_extractall def _prepare_tarball_or_zip(self): A. De Bonis else: def # Ends with .tar.gz, .tar.bz2, or .tar.xz safe_extractall(): 54 unsafe = []= os.path.splitext(self.filename)[1] suffix for name in self.names(): self._file = tarfile.open(self.filename, "r:" + suffix[1:]) if not self.is_safe(name): self._names = self._file.getnames unsafe.append(name) ifself._unpack unsafe: = safe_extractall raise ValueError("unsafe to unpack: {}".format(unsafe)) 5 self._file.extractall() if self.filename.endswith(".zip"): self._file = zipfile.ZipFile(self.filename) self._names = self._file.namelist
def _prepare(self): if self.filename.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".zip")): self._prepare_tarball_or_zip() elif self.filename.endswith(".gz"): 02/12/20 self._prepare_gzip() else: raise ValueError("unreadable: {}".format(self.filename)) This method delegates the preparation to suitable methods. For tarballs and zip files the necessary code is very similar, so they are prepared in the same method. But gzipped files are handled differently and so have their own sepa- rate method. The preparation methods must assign callables to the self._names and self._un- Il Design Pattern Facade: un esempio pack variables, so that these can be called in the names() and unpack() methods we have just seen. def _prepare_tarball_or_zip(self): • Questo metodo comincia con il creare una def safe_extractall(): funzione innestata safe_extractall() che unsafe = [] controlla tutti i nomi dell’archivio e lancia for name in self.names(): if not self.is_safe(name): ValueError se qualcuno di essi non è safe. unsafe.append(name) if unsafe: • Se tutti i nomi sono safe viene invocato o 62 Chapter 2. Structural raise ValueError("unsafe to unpack:Design Patterns in Python {}".format(unsafe)) il metodo tarball.TarFile.extractall() self._file.extractall() if self.filename.endswith(".zip"): oppure il metodo def unpack(self): self._fileis= None: zipfile.ZipFile(self.filename) zipfile.ZipFile.extractall(). if self._file self._names = self._file.namelist self._prepare() self._unpack = safe_extractall self._unpack() else: # Ends with .tar.gz, .tar.bz2, or .tar.xz suffix = all This method unpacks os.path.splitext(self.filename)[1] the archive’s files, but as we will see, only if all of their self._file = tarfile.open(self.filename, "r:" + suffix[1:]) names are “safe”. self._names = self._file.getnames self._unpack = safe_extractall def _prepare(self): if self.filename.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".zip")): Programmazione Avanzata a.a. 2020-21 self._prepare_tarball_or_zip() A. De Bonis elif self.filename.endswith(".gz"): www.it-ebooks.info self._prepare_gzip() 55 else: raise ValueError("unreadable: {}".format(self.filename)) This method delegates the preparation to suitable methods. For tarballs and zip files the necessary code is very similar, so they are prepared in the same method. But gzipped files are handled differently and so have their own sepa- rate method. Il Design Pattern Facade: un esempio The preparation methods must assign callables to the self._names and self._un- pack variables, so that these can be called in the names() and unpack() methods we have just seen. • A seconda dell’estensione del nome def _prepare_tarball_or_zip(self): def safe_extractall(): dell’archivio, viene aperto un tarball.TarFile unsafe = [] o uno zipfile.ZipFile e assegnato a for name in self.names(): self._file. if not self.is_safe(name): • self._names viene settata al metodo unsafe.append(name) if unsafe: bound corrispondente (namelist() o raise ValueError("unsafe to unpack: {}".format(unsafe)) getnames() ) self._file.extractall() • self._unpack viene settata alla funzione if self.filename.endswith(".zip"): safe_extractall() appena creata. Questa self._file = zipfile.ZipFile(self.filename) self._names = self._file.namelist funzione è una chiusura che ha catturato self._unpack = safe_extractall self e quindi può accedere a self._file e else: # Ends with .tar.gz, .tar.bz2, or .tar.xz chiamare il metodo appropriato suffix = os.path.splitext(self.filename)[1] extractall() self._file = tarfile.open(self.filename, "r:" + suffix[1:]) self._names = self._file.getnames self._unpack = safe_extractall Programmazione Avanzata a.a. 2020-21 A. De Bonis www.it-ebooks.info 56 6
assigned an object reference to the Form.update_ui() method that is bound to a particular instance of the form (self). A bound method can be called directly; for example, bound(). An unbound method is a method with no associated instance. For example, 02/12/20 if we write unbound = Form.update_ui, unbound is assigned an object reference to the Form.update_ui() method, but with no binding to any particular in- stance. This means that if we want to call the unbound method, we must provide a suitable instance as its first argument; for example, form = Form(); unbound(form). (Strictly speaking, Python 3 doesn’t actually have unbound methods, so unbound is really the underlying function object, although this only Il Design Pattern Facade: un esempio makes a difference in some metaprogramming corner cases.) def is_safe(self, filename): return not (filename.startswith(("/", "\\")) or (len(filename) > 1 and filename[1] == ":" and filename[0] in string.ascii_letter) or re.search(r"[.][.][/\\]", filename)) • AUnmaliciously created file di archivio creato archive in modo filepotrebbe, malizioso that is una unpacked could overwrite volta spacchettato, sovrascrivereimportant importanti file di sistema rimpiazzandoli con file non funzionanti o system files with nonfunctional or sinister replacements. In view of this, we pericolosi. In considerazione • should never open di ciò, archives non dovrebbero thatmai essere aperti contain filesarchivi withcontenenti absolutefile con path paths or with rela- assoluti o che includono path relative ed evitare di aprire gli archivi con i privilegi di un utente tive path components, and we should always open archives as an unprivileged come root o Administrator. • user (i.e., is_safe() never False restituisce as root or Administrator). se il nome del file comincia con un forward slash o con un backslash (cioè un path assoluto) o contiene ../ o ..\ (cioè un path relativo che potrebbe condurre This method returns False if the filename it is given starts with a forward slash ovunque), oppure comincia con D: dove D indica un’unità disco di Windows. or a backslash (i.e., an absolute path), or contains ../ or ..\ (a relative path Programmazione Avanzata a.a. 2020-21 that could lead anywhere), or startsA. Dewith Bonis D: where D is a Windows drive letter. 57 64 www.it-ebooks.info Chapter 2. Structural Design Patterns in Python In other words, any filename that is absolute or that has relative components is Il Design Pattern Facade: un esempio considered to be unsafe. For any other filename the method returns True. def _prepare_gzip(self): self._file = gzip.open(self.filename) filename = self.filename[:-3] self._names = lambda: [filename] def extractall(): with open(filename, "wb") as file: file.write(self._file.read()) self._unpack = extractall Questo metodo fornisce un object file aperto per self._file e assegna callable adatti a self._names e This method provides an open file object for self._file and assigns suitable self._unpack. La funzione extractall(), callables legge e scrive to self._names and dati. self._unpack. For the extractall() function, we have Ilto pattern readFacade permette and write di creare the data interfacce ourselves. semplici e comode che ci permettono di ignorare i dettagli di basso livello. Uno svantaggio di questo design pattern potrebbe essere quello di non consentire un controllo più The fine. Façade Pattern can be very useful for creating simplified and convenient in- Tutttavia, terfaces. un facade non nasconte The upside o elimina is that we arele funzionalità insulated delfrom sistema sottostantedetails; low-level e così è possibile usare un the downside facade passando però a classi di più basso livello se abbiamo bisogno di un maggiore controllo. is that we may have to give up fine control. Programmazione However, Avanzata a.a. 2020-21 a façade doesn’t hide or do away with the underlying functionality,A.so De Bonis we can always use the façade most of 58 the time, and just drop down to lower-level classes if we need more control. The Façade and Adapter Patterns have a superficial similarity. The difference is that a façade provides a simple interface on top of a complicated interface, whereas an adapter provides a standardized interface on top of another (not necessarily complicated) interface. Both patterns can be used together. For ex- 7 ample, we might define an interface for handling archive files (tarballs, zip files, Windows .cab files, and so on), use an adapter for each format, and layer a façade on top so that users would not need to know or care about which particular file
02/12/20 I context manager I context manager consentono di allocare e rilasciare risorse quando vogliamo L’esempio più usato di context manager è lo statement with. with open('some_file’, 'w') as opened_file: opened_file.write('Hola!') Questo codice apre il file, scrive alcuni dati in esso e lo chiude. Se si verifica un errore mentre si scrivono i dati, esso cerca di chiuderlo. Il codice in alto è equivalente a file = open('some_file’, 'w’) try: file.write('Hola!’) finally: file.close() Programmazione Avanzata a.a. 2020-21 A. De Bonis 59 I context manager • è possibile implementare un context manager con una classe. class File: def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): self.file_obj.close() Programmazione Avanzata a.a. 2020-21 A. De Bonis 60 8
02/12/20 I context manager • è sufficiente definire __enter__() ed __exit__() per poter usare la classe File in uno statement with. with File('demo.txt', 'w') as opened_file: opened_file.write('Hola!') Programmazione Avanzata a.a. 2020-21 A. De Bonis 61 I context manager • Come funziona lo statement with: • Immagazzina il metodo __exit__() della classe File • Invoca il metodo __enter__() della classe File • Il metodo __enter__ restituisce il file object per il file aperto. • L’object file è passato a opened_file. • Dopo che è stato eseguito il blocco al suo interno, lo statement with invoca il metodo __exit__() • Il metodo __exit__() chiude il file with File('demo.txt', 'w') as opened_file: opened_file.write('Hola!') Programmazione Avanzata a.a. 2020-21 A. De Bonis 62 9
02/12/20 I context manager • Se tra il momento in cui viene passato l’object file a opened_file e il momento in cui viene invocata __exit__, si verifica un’eccezione allora Python passa type, value e traceback dell’eccezione come argomenti a __exit__() per decidere come chiudere il file e se eseguire altri passi. In questo esempio gli argomenti di exit non influiscono sul suo comportamento. with File('demo.txt', 'w') as opened_file: opened_file.write('Hola!') Programmazione Avanzata a.a. 2020-21 A. De Bonis 63 I context manager • Se il file object lanciasse un’eccezione, come nel caso in cui provassimo ad accedere ad un metodo non supportato dal file object: with File('demo.txt', 'w') as opened_file: opened_file.undefined_function('Hola!') • with eseguirebbe i seguenti passi: 1. passerebbe type, value e traceback a __exit__() 2. permetterebbe a __exit__() di gestire l’eccezione 3. Se __exit__() restituisse True allora l’eccezione non verrebbe rilanciata dallo statement with. 4. Se __exit__() restituisse un valore diverso da True allora l’eccezione verrebbe lanciata dallo statement with Programmazione Avanzata a.a. 2020-21 A. De Bonis 64 10
02/12/20 I context manager with File('demo.txt', 'w') as opened_file: opened_file.undefined_function('Hola!') Nel nostro esempio, __exit__() restituisce (implicitamente) None per cui with lancerebbe l’eccezione: Traceback (most recent call last): File "", line 2, in AttributeError: ‘file’ object has no attribute 'undefined_function' Programmazione Avanzata a.a. 2020-21 A. De Bonis 65 I context manager Il metodo __exit__() in basso invece gestisce l’eccezione: class File(object): def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): print("Exception has been handled") self.file_obj.close() return True with File('demo.txt', 'w') as opened_file: opened_file.undefined_function() Programmazione Avanzata a.a. 2020-21 A. De Bonis 66 11
02/12/20 I context manager • è possibile implementare un context manager con un generatore utilizzando il modulo contextlib. Il decoratore Python contextmanager trasforma il generatore open_file in un oggetto GeneratorContextManager from contextlib import contextmanager @contextmanager def open_file(name): f = open(name, 'w’) yield f f.close() • Si usa in questo modo with open_file('some_file’) as f: f.write('hola!') Programmazione Avanzata a.a. 2020-21 A. De Bonis 67 I context manager • Nel punto in cui c’è yield il blocco nello statement with viene eseguito. • Il generatore riprende all’uscita del blocco. • Se nel blocco si verifica un’eccezione non gestita, essa viene rilanciata nel generatore nel punto dove si trova yield. • è possibile usare uno statement try...except...finally per catturare l’errore. • Se un’eccezione è catturata solo al fine di registrarla o per svolgere qualche azione (piuttosto che per sopprimerla), il generatore deve rilanciare l’eccezione. Altrimenti il generatore context manager indicherà allo statement with che l’eccezione è stata gestita e l’esecuzione riprenderà dallo statement che segue lo statement with. Programmazione Avanzata a.a. 2020-21 A. De Bonis 68 12
02/12/20 I context manager • Lo statement try...finally garantisce che il file venga chiuso anche nel caso si verifichi un’ eccezione nel blocco del with from contextlib import contextmanager @contextmanager def open_file(name): f = open(name, 'w’) try: yield f finally: f.close() Programmazione Avanzata a.a. 2020-21 A. De Bonis 69 Programmazione Avanzata Design Pattern: Factory Method Programmazione Avanzata a.a. 2020-21 A. De Bonis 70 13
02/12/20 Factory Method Pattern • È un design pattern creazionale. • Si usa quando vogliamo definire un’interfaccia o una classe astratta per creare degli oggetti e delegare le sue sottoclassi a decidere quale classe istanziare quando viene richiesto un oggetto. • Particolarmente utile quando una classe non può conoscere in anticipo la classe degli oggetti che deve creare. Programmazione Avanzata a.a. 2020-21 A. De Bonis 71 Factory Method Pattern: un’applicazione • Esempio: Consideriamo un framework per delle applicazioni ciascuna delle quali elabora documenti di diverso tipo. • Abbiamo bisogno di due astrazioni: la classe Application e la classe Document • La classe Application gestisce i documenti e li crea su richiesta dell’utente, ad esempio, quando l’utente seleziona Open o New dal menu. • Entrambe le classi sono astratte e occorre definire delle loro sottoclassi per poter realizzare le implementazioni relative a ciascuna applicazione • Ad esempio, per creare un’applicazione per disegnare, definiamo le classi DrawingApplication e DrawingDocument. • Definiamo un’interfaccia per creare un oggetto ma lasciamo alle sottoclassi decidere quali classi istanziare. Programmazione Avanzata a.a. 2020-21 A. De Bonis 72 14
FACTORY METHOD Class Creational02/12/20 Intent Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. Factory Method Pattern: un’applicazione Also Known As PoichéConstructor • Virtual la particolare sottoclasse di Document da istanziare dipende dalla particolare applicazione, la classe Application non può fare previsioni riguardo alla sottoclasse di Document da istanziare Motivation La classe Application • Frameworks sa solo use abstract classes quando to define deveand essere creato maintain un nuovo between relationships documento objects. ma non is A framework neoften conosce il tipo. for creating these objects as well. responsible Problema: • Consider devono essere a framework istanziate delle for applications that classi ma si conoscono can present solo multiple documents to delle classi astratte che non possono essere istanziate the user. Two key abstractions in this framework are the classes Application and Il Factory method • Document. pattern Both classes risolve questo are abstract, and clients problema have toincapsulando subclass them to realize l’informazione their riguardo implementations. application-specific alla sottoclasse diToDocument da creare create a drawing e application, for sposta questa example, informazione we define the classes all’esterno DrawingApplication del framework.and DrawingDocument. The Application class is responsible for managing Documents and will create them as required—when the user selects Open or New from a menu, for example. Programmazione Avanzata a.a. 2020-21 A. De Bonis 73 Because the particular Document subclass to instantiate is application-specific, the Application class can't predict the subclass of Document to instantiate—the Ap- plication class only knows when a new document should be created, not what kind of Document to create. This creates a dilemma: The framework must instantiate classes, but it only knows about abstract classes, which it cannot instantiate. The Factory Method pattern offers a solution. It encapsulates the knowledge Factory Method of which Document framework. Pattern: subclass to create un’applicazione and moves this knowledge out of the Programmazione Avanzata a.a. 2020-21 A. De Bonis 74 15
02/12/20 Factory Method Pattern: un’applicazione • Le sottoclassi di Application ridefiniscono il metodo astratto CreateDocument per restituire la sottoclasse appropriata di Document • Una volta istanziata, la sottoclasse di Application può creare istanze di Document per specifiche applicazioni senza dover conoscere le sottoclassi delle istanze create (CreateDocument) • CreateDocument è detto factory method perché è responsabile della creazione degli oggetti Programmazione Avanzata a.a. 2020-21 A. De Bonis 75 Factory Method Pattern: un semplice esempio class Pizza(): def __init__(self): self._price = None def get_price(self): return self._price Programmazione Avanzata a.a. 2020-21 A. De Bonis 76 16
02/12/20 Factory Method Pattern: un semplice esempio class HamAndMushroomPizza(Pizza): def __init__(self): self._price = 8.5 class DeluxePizza(Pizza): def __init__(self): self._price = 10.5 class HawaiianPizza(Pizza): def __init__(self): self._price = 11.5 Programmazione Avanzata a.a. 2020-21 A. De Bonis 77 Factory Method Pattern: un semplice esempio • PizzaFactory fornisce il metodo createPizza che è statico per cui può essere invocato quando non è stata ancora creata una pizza class PizzaFactory: @staticmethod def create_pizza(pizza_type): if pizza_type == 'HamMushroom': return HamAndMushroomPizza() elif pizza_type == 'Deluxé: return DeluxePizza() elif pizza_type == 'Hawaiian': return HawaiianPizza() Programmazione Avanzata a.a. 2020-21 A. De Bonis 78 17
02/12/20 Factory Method Pattern: un semplice esempio if __name__ == '__main__': for pizza_type in ('HamMushroom', 'Deluxé, 'Hawaiian'): print('Price of {0} is {1}'.format(pizza_type, PizzaFactory.create_pizza(pizza_type).get_price()) • il tipo di pizza avrebbe potuto essere fornito dall’utente • il tipo di pizza indicato dall’utente potrebbe essere stato inserito successivamente nel menu e la classe concreta corrispondente creata successivamente al main • occorre modificare solo la factory Programmazione Avanzata a.a. 2020-21 A. De Bonis 79 Factory Method Pattern: un semplice esempio • Che cosa accade se vogliamo creare diversi tipi di negozi ciascuno dei quali vende pizze nello stile di una certà città • Creiamo una classe astratta PizzaStore al cui interno c’è il metodo astratto create_pizza • Dalla classe PizzaStore deriviamo NYPizzaStore, ChicagoPizzaStore e così via. Queste sottoclassi sovrascriveranno il metodo astratto. La decisione sul tipo di pizza da creare è presa dal metodo create_pizza della specifica sottoclasse. • analogamente a quanto accadeva nel framework per la gestione dei documenti • PizzaStore avrà anche un metodo orderPizza() che invoca createPizza ma non ha idea su quale pizza verrà creata fino a che non verra` creata una classe concreta di PizzaStore Programmazione Avanzata a.a. 2020-21 A. De Bonis 80 18
02/12/20 Factory Method Pattern: un semplice esempio from abc import ABC, abstractmethod class Pizza(ABC): @abstractmethod def prepare(self): pass def bake(self): print("baking pizza for 12min in 400 degrees..") def cut(self): print("cutting pizza in pieces") def box(self): print("putting pizza in box") Programmazione Avanzata a.a. 2020-21 A. De Bonis 81 Factory Method Pattern: un semplice esempio class NYStyleCheesePizza(Pizza): def prepare(self): print("preparing a New York style cheese pizza..") class ChicagoStyleCheesePizza(Pizza): def prepare(self): print("preparing a Chicago style cheese pizza..") class NYStyleGreekPizza(Pizza): def prepare(self): print("preparing a New York style greek pizza..") class ChicagoStyleGreekPizza(Pizza): def prepare(self): print("preparing a Chicago style greek pizza..") Programmazione Avanzata a.a. 2020-21 A. De Bonis 82 19
02/12/20 Factory Method Pattern: un semplice esempio class PizzaStore(ABC): @abstractmethod def _createPizza(self, pizzaType: str) -> Pizza: pass def orderPizza(self, pizzaType): pizza = self._createPizza(pizzaType) pizza.prepare() pizza.bake() pizza.cut() pizza.box() Programmazione Avanzata a.a. 2020-21 A. De Bonis 83 Factory Method Pattern: un semplice esempio class NYPizzaStore(PizzaStore): def _createPizza(self, pizzaType: str) -> Pizza: pizza = None if pizzaType == 'Greek’: pizza = NYStyleGreekPizza() elif pizzaType == 'Cheese’: pizza = NYStyleCheesePizza() else: print("No matching pizza found in the NY pizza store...") return pizza Programmazione Avanzata a.a. 2020-21 A. De Bonis 84 20
02/12/20 Factory Method Pattern: un semplice esempio class ChicagoPizzaStore(PizzaStore): def _createPizza(self, pizzaType: str) -> Pizza: pizza = None if pizzaType == 'Greek’: pizza = ChicagoStyleGreekPizza() elif pizzaType == 'Cheese’: pizza = ChicagoStyleCheesePizza() else: print("No matching pizza found in the Chicago pizza store...") return pizza Programmazione Avanzata a.a. 2020-21 A. De Bonis 85 18 Chapter 1. Creational Design Patterns in Python Factory Method Pattern: un esempio We will begin by reviewing the top-level code that instantiates and prints the boards. Next, we will look at the board classes and some of the piece classes— Voglio creare starting withuna scacchiera hard-coded per la dama classes. Then ed weuna willper gli scacchi review some variations that allow us to avoid hard-coding classes and at the same time use fewer lines of code. def main(): checkers = CheckersBoard() print(checkers) chess = ChessBoard() print(chess) This function is common to all versions of the program. It simply creates each type of board and prints it to the console, relying on the AbstractBoard’s __str__() method to convert the board’s internal representation into a string. Programmazione Avanzata a.a. 2020-21 A. De Bonis BLACK, WHITE = ("BLACK", "WHITE") 86 class AbstractBoard: def __init__(self, rows, columns): self.board = [[None for _ in range(columns)] for _ in range(rows)] self.populate_board() 21 def populate_board(self): raise NotImplementedError()
18 Chapter 1. Creational Design Patterns in Python 02/12/20 We will begin by reviewing the top-level code that instantiates and prints the boards. Next, we will look at the board classes and some of the piece classes— starting with hard-coded classes. Then we will review some variations that allow us to avoid hard-coding classes and at the same time use fewer lines of code. def main(): checkers = CheckersBoard() print(checkers) Factory Method Pattern: un esempio chess = ChessBoard() print(chess) • la scacchiera è una lista di liste (righe) di stringhe di un singolo carattere • __init__ Inizializza Thisla scacchiera function is con tutte le posizioni common vuote eof to all versions poithe invoca populate_board program. per It simply inserireeach creates i type of board and prints it to the console, relying on the AbstractBoard’s __str__() pezzi del gioco method to convert the board’s internal representation into a string. • populate_board è astratto BLACK, WHITE = ("BLACK", "WHITE") • La funzione console() class AbstractBoard: restituisce una stringa che rappresenta il def __init__(self, rows, columns): pezzo ricevuto in input self.board = [[None for _ in range(columns)] for _ in range(rows)] sul colore di sfondo self.populate_board() passato come secondo argomento. def populate_board(self): raise NotImplementedError() def __str__(self): squares = [] for y, row in enumerate(self.board): for x, piece in enumerate(row): square = console(piece, BLACK if (y + x) % 2 else WHITE) squares.append(square) squares.append("\n") return "".join(squares) Programmazione Avanzata a.a. 2020-21 A. De Bonis The BLACK and WHITE constants are used here to indicate each square’s back- 87 ground color. In later variants they are also used to indicate each piece’s color. This class is quoted from gameboard1.py, but it is the same in all versions. It would have been more conventional to specify the constants by writing: BLACK, 1.3. Factory Method Pattern WHITE = range(2). However, using strings is much more helpful when it comes to 19 debugging error messages, and should be just as fast as using integers thanks to Python’s smart interning and identity checks. returns a string representing the given piece on the given background color. (On The board is represented by a list of rows of single-character strings—or None for Unix-like systems thissquares. unoccupied string includes escape The console() codes function (notto colorbut shown, thein background.) the source code), Factory We could haveMethod Pattern:a formally made the AbstractBoard un esempio abstract class by giving it a www.it-ebooks.info metaclass of abc.ABCMeta (as we did for the AbstractFormBuilder class; 12 ➤). How- ever, here we have chosen to use a different approach, and simply raise a NotIm- • La classe per creare scacchiere per il gioco della dama plementedError exception for any methods we want subclasses to reimplement. class CheckersBoard(AbstractBoard): def __init__(self): super().__init__(10, 10) def populate_board(self): for x in range(0, 9, 2): for row in range(4): column = x + ((row + 1) % 2) self.board[row][column] = BlackDraught() self.board[row + 6][column] = WhiteDraught() Programmazione Avanzata a.a. 2020-21 A. De Bonis This subclass is used to create a representation of a 10 × 10 international 88 checkers board. This class’s populate_board() method is not a factory method, since it uses hard-coded classes; it is shown in this form as a step on the way to making it into a factory method. class ChessBoard(AbstractBoard): 22 def __init__(self): super().__init__(8, 8)
plementedError exception for any methods we want subclasses to reimplement. class CheckersBoard(AbstractBoard): def __init__(self): super().__init__(10, 10) 02/12/20 def populate_board(self): for x in range(0, 9, 2): for row in range(4): column = x + ((row + 1) % 2) self.board[row][column] = BlackDraught() self.board[row + 6][column] = WhiteDraught() Factory MethodThis class’s Pattern: un esempio This subclass is used to create a representation of a 10 × 10 international checkers board. method is not a factory method, populate_board() since it uses hard-coded classes; it is shown in this form as a step on the way to ptg • Lamaking classeitper into ascacchiere per il gioco degli scacchi factory method. class Method 1.3. Factory ChessBoard(AbstractBoard): Pattern 19 def __init__(self): returns a string representing the given piece on the given background color. (On super().__init__(8, Unix-like systems 8) this string includes escape codes to color the background.) We could have made the AbstractBoard a formally abstract class by giving it a def populate_board(self): metaclass of abc.ABCMeta (as we did for the AbstractFormBuilder class; 12 ➤). How- self.board[0][0] ever, here we have = BlackChessRook() chosen to use a different approach, and simply raise a NotIm- self.board[0][1] plementedError exception = BlackChessKnight() for any methods we want subclasses to reimplement. ... class CheckersBoard(AbstractBoard): self.board[7][7] = WhiteChessRook() for column in range(8): def __init__(self): self.board[1][column] super().__init__(10, 10) = BlackChessPawn() self.board[6][column] = WhiteChessPawn() def populate_board(self): Thisfor x in range(0, version 9, 2): of the ChessBoard ’s populate_board() method—just like the Checkers- for row in range(4): Programmazione Board’s one—is not a factory method, but itAvanzata doesa.a.illustrate 2020-21 how the chess board is column = x + ((row + 1) % 2) A. De Bonis populated.self.board[row][column] = BlackDraught() 89 self.board[row + 6][column] = WhiteDraught() class Piece(str): This subclass is used to create a representation of a 10 × 10 international __slots__ checkers board. = () populate_board() method is not a factory method, This class’s since it uses hard-coded classes; it is shown in this form as a step on the way to ptg11539634 making it into a factory method. class ChessBoard(AbstractBoard): www.it-ebooks.info Factory Method Pattern: un esempio def __init__(self): super().__init__(8, 8) • La def classe base per i pezzi populate_board(self): self.board[0][0] = BlackChessRook() • Si è scelto di creare una classe che discende da str invece che usare self.board[0][1] = BlackChessKnight() ... direttamente str per poter facilmente testare se un oggetto z è un self.board[7][7] = WhiteChessRook() pezzo del gioco con isinstance(z,Piece) for column in range(8): self.board[1][column] = BlackChessPawn() • ponendo __slots__={} ci assicuriamo che gli oggetti di tipo Piece non self.board[6][column] = WhiteChessPawn() abbiano This version ofvariabili di’s populate_board() the ChessBoard istanza method—just like the Checkers- Board’s one—is not a factory method, but it does illustrate how the chess board is populated. class Piece(str): __slots__ = () www.it-ebooks.info Programmazione Avanzata a.a. 2020-21 A. De Bonis 90 23
02/12/20 Factory Method Pattern: un esempio • La classe pedina nera e la classe re bianco • le classi20per gli altri pezzi sono create Chapterin1.modo analogo Creational Design Patterns in Python • Ognuna di queste classi è una sottoclasse immutabile di Piece che è This class serves as a base class for pieces. We could have simply used str, but sottoclasse that woulddinotstrhave allowed us to determine if an object is a piece (e.g., using • Inizializzata conPiece) isinstance(x, la stringa ). Usingdi un unico __slots__ = ()carattere (ilinstances ensures that carattere Unicode have no data, che a topic we’ll discuss later on (§2.6, ➤ 65). rappresenta il pezzo) class BlackDraught(Piece): __slots__ = () def __new__(Class): return super().__new__(Class, "\N{black draughts man}") class WhiteChessKing(Piece): __slots__ = () def __new__(Class): return super().__new__(Class, "\N{white chess king}") Programmazione Avanzata a.a. 2020-21 A. De Bonisused for all the piece classes. Every These two classes are models for the pattern one is an immutable Piece subclass (itself a str subclass) that is initialized 91 with a one-character string holding the Unicode character that represents the relevant piece. There are fourteen of these tiny subclasses in all, each one differing only by its class name and the string it holds: clearly, it would be nice ptg11539 to eliminate all this near-duplication. def populate_board(self): for x in range(0, 9, 2): Factory Method Pattern: un esempio for y in range(4): column = x + ((y + 1) % 2) for row, color in ((y, "black"), (y + 6, "white")): • Notiamo che qui la stringa cheself.board[row][column] indica il pezzo è assegnata da __new__ = create_piece("draught", • Il metodo __new__ non prende argomenti in quanto la stringa che rappresenta il pezzo è 20 color) Chapter 1. Creational Design Patterns in Python codificato all’interno del metodo. This new version of the CheckersBoard.populate_board() method (quoted from • TypeError: This class__new__() serves) as takes 1 positional argument but 2 were given gameboard2.py is aa factory base class for pieces. method, We since it could depends have on a simply used str, butfac- new create_piece() • Per i tipi tory thatche estendono would not have tipi immutable, function rather allowed us to come str,classes. determine than on hard-coded l’inizializzazione if an object The is è fatta(e.g., a piece create_piece()da __new__. using function isinstance(x, returns an Piece) object ). ofUsing the __slots__ =type appropriate () ensures (e.g., a that • https://docs.python.org/3/reference/datamodel.html : __new__() is intendedinstances BlackDraught orhave a no data,mainly WhiteDraught ), to allow a topic subclasses we’ll depending discuss on its later of immutable on(like arguments. types (§2.6, This int, 65). ➤str, version or tuple)oftothe program customize has a similar instance creation.Chess- Board.populate_board() method (not shown), which also uses string color and class BlackDraught(Piece): piece names and the same create_piece() function. __slots__ = () def create_piece(kind, color): defif __new__(Class): kind == "draught": return super().__new__(Class, return "\N{black draughts eval("{}{}()".format(color.title(), man}") kind.title())) return eval("{}Chess{}()".format(color.title(), kind.title())) class WhiteChessKing(Piece): __slots__ = () def __new__(Class): return super().__new__(Class, "\N{white chess king}") www.it-ebooks.info Programmazione Avanzata a.a. 2020-21 A. De Bonis These two classes are models for the pattern used for all the piece classes. Every one is an immutable Piece subclass (itself a str subclass) that is initialized 92 with a one-character string holding the Unicode character that represents the relevant piece. There are fourteen of these tiny subclasses in all, each one differing only by its class name and the string it holds: clearly, it would be nice ptg11539634 to eliminate all this near-duplication. def populate_board(self): 24 for x in range(0, 9, 2): for y in range(4): column = x + ((y + 1) % 2) for row, color in ((y, "black"), (y + 6, "white")):
class BlackDraught(Piece): __slots__ = () 02/12/20 def __new__(Class): return super().__new__(Class, "\N{black draughts man}") class WhiteChessKing(Piece): __slots__ = () def __new__(Class): Factory Method Pattern: un esempio return super().__new__(Class, "\N{white chess king}") • Questa nuova versione del metodo CheckersBoard.populate_board() è un These factory two method in quanto dipende 20 classes are models for dalla the pattern Chapter factory usedfunction 1. Creational create_piece() for allPatterns Design the piece classes. Every in Python one• is an immutable subclass (itself a subclass) Nella versione precedente il tipo di pezzo era indicato nel codice Piece str that is initialized with a Thisone-character string class serves as a holding base class theWeUnicode for pieces. could have character simply used strthat , butrepresents • La that funzione would create_piece() not have allowedrestituisce us un oggetto to determine del tipo if an object is appropriato a piece (e.g., in (ad using the relevant esempio, piece. There BlackDraught are fourteen o WhiteDraught) of these tiny in basethat ai suoisubclasses argomenti. all, each one isinstance(x, Piece)). Using __slots__ = () ensures instances have no data, differing onlywe’ll by discuss its class latername and the string it holds: clearly, it would be nice • Il metodo a topic ChessBoard.populate_board() on (§2.6, to eliminate all this near-duplication. ➤ 65). viene anch’esso modificato in modo da usare la stessa funzione create_piece() class BlackDraught(Piece): invocata qui. def populate_board(self): __slots__ = () for in range(0, 9, 2): defx __new__(Class): for y in return range(4): super().__new__(Class, "\N{black draughts man}") column = x + ((y + 1) % 2) class WhiteChessKing(Piece): for __slots__ = ()row, color in ((y, "black"), (y + 6, "white")): self.board[row][column] = create_piece("draught", def __new__(Class): color) return super().__new__(Class, "\N{white Programmazione chess king}") Avanzata a.a. 2020-21 A. De Bonis These 93 This new two classes version of are themodels for the pattern used for all the piece classes. CheckersBoard.populate_board() method Every (quoted from one is an immutable Piece subclass (itself a str subclass) that is initialized gameboard2.py) is a factory method, since it depends on a new create_piece() fac- with a one-character string holding the Unicode character that represents tory function rather the relevant piece.than Thereon arehard-coded classes. fourteen of these The create_piece() tiny subclasses in all, each one function returnsdiffering an objectonlyofbythe appropriate its class name and thetype (e.g., string a BlackDraught it holds: clearly, it wouldorbe a nice WhiteDraught), ptg1 to eliminate all this near-duplication. depending on its arguments. This version of the program has a similar Chess- Board.populate_board() method (not shown), which also uses string color and Factory Method Pattern: un esempio def populate_board(self): piece names and for the x in same create_piece() range(0, 9, 2): function. for y in range(4): def• Questa funzione factory usa la funzione built-in eval() per creare column = x + ((y + 1) % 2) create_piece(kind, color): for row, color in ((y, "black"), (y + 6, "white")): istanze della classe if kind == "draught": self.board[row][column] = create_piece("draught", return eval("{}{}()".format(color.title(), color) kind.title())) • Ad esempio se gli argomenti sono "knight" and "black", la stringa return eval("{}Chess{}()".format(color.title(), kind.title())) valutata This newsarà "BlackChessKnight()". version of the CheckersBoard.populate_board() method (quoted from gameboard2.py) is a factory method, since it depends on a new create_piece() fac- • In tory generale functionèrather meglio thannon usare eval on hard-coded per eseguire classes. il codice The create_piece() function rappresentato da un’espressione perché è potenzialmente returns an object of the appropriate type (e.g., a BlackDraught or rischioso a WhiteDraught), dalBoard.populate_board() momento che permette di eseguire il codice rappresentato da una depending on its arguments. This version of the program has a similar Chess- method (not shown), which also uses string color and www.it-ebooks.info qualsiasi espressione piece names and the same create_piece() function. def create_piece(kind, color): if kind == "draught": return eval("{}{}()".format(color.title(), kind.title())) return eval("{}Chess{}()".format(color.title(), kind.title())) Programmazione Avanzata a.a. 2020-21 A. De Bonis 94 www.it-ebooks.info 25
You can also read