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
102/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.infoopen 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
402/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.namelistdef _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
6assigned 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 file02/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
802/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
902/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
1002/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
1102/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
1202/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
1302/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
14FACTORY 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
1502/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
1602/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
1702/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
1802/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
1902/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
2002/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
2302/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
25You can also read