vuoi
o PayPal
tutte le volte che vuoi
Altro metodo per rendere una classe immutabile è usare un costruttore privato e
affiancare uno SFM 6
ITEM 14
Favorire la composizione rispetto all’ereditarietà.
L’ereditarietà e utile per il riutilizzo di codice. Tramite i package si hanno classi e
sottoclassi sotto lo stesso controllo. Ma l’ereditarietà viola l’incapsulamento, in quanto
una sottoclasse dipende dai dettagli implementativi della superasse relativa.
Inoltre l’ereditarietà tra classi concrete tra package è pericolosa. Si ha anche il problema
della Classe Fragile (classe base), ovvero cambiamenti effettuati alla superclasse
interessano a catena anche il comportamento delle sottoclassi, le quali terminano di
operare correttamente.
Esempio
public class InstrumentedHashSet extends HashSet {
private int addCount = 0;
public InstrumentedHashSet() {}
public InstrumentedHashSet(Collection c) {super(c);}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
public boolean add(Object o) {addCount++; return super.add(o);}
public boolean addAll(Collection c) {
addCount += c.size();
return super.addAll(c); //Utilizza addAll di HashSet
}
public int getAddCount() {return addCount;}
}
In questo caso se il metodo addAll di HashSet cambiasse non sarebbe più garantito il
corretto funzionamento della classe InstrumentedHashSet, per questo motivo è
necessario seguire delle linee guida per evitare il problema della classe fragile.
Usare librerie stabili: potrebbe sembrare banale ma, l'uso di elementi standard è
• un primo passo verso la stabilità in modo che il problema non sorga;
Usare sempre attributi privati e fornire getter e setter per accedervi: in questo
• modo si rafforza l'incapsulamento anche in presenza di ereditarietà e si ottiene un
design più pulito;
Usare programmazione difensiva nel caso di attributi complessi; questo come
• corollario al precedente punto;
Programmare per interfacce non per classi (VEDI ESEMPIO SOTTO). Un
• classico adagio della programmazione ad oggetti in questo caso quanto mai utile:
le interfacce non forniscono implementazione, quindi, non presentano il problema;
Rendere i metodi ereditabili final: in questo modo non può verificarsi l'overriding
• di metodi, azione che potrebbe essere ulteriore causa di fragilità;
Evitare l'ereditarietà. Il riuso del codice è importante ma, non deve spingere alla
• creazioni di gerarchie dalle dimensioni sensibili o volute per il solo riuso di
implementazioni;
Non eliminare bensì deprecare. Gli sviluppatori Java spesso incontrano il
• concetto di deprecato e in questa sede ne vediamo l'utilità. Eliminare un pezzo di
classe vuol dire, in modo ricorsivo, alterare l'equilibrio funzionale nella gerarchia
7
che vede la data classe come root-class. Indicando come non più usabile
(deprecato) un dato elemento si fornisce l'opportuno mezzo per non alterare
rapporti preesistenti tra generalizzazioni-specializzazioni e, grazie ad una giusta
documentazione, si può condurre lo sviluppatore verso funzionalità nuove o
sostitutive.
public class InstrumentedSet implements Set {
// ... leverages the existence of an interface Set, which captures the
functionality of HashSet
private final Set s; // private reference to the composed object
private int addCount = 0;
public InstrumentedSet(Set s) {this.s = s;} //reference installed by the
constructor
public boolean add(Object o) {addCount++; return s.add(o);}
public boolean addAll(Collection c) {addCount += c.size(); return
s.addAll(c);}
public int getAddCount() {return addCount;}
// Forwarding methods: each and every one :(
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator iterator() { return
s.iterator(); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection c){ return s.containsAll(c); }
public boolean removeAll(Collection c){ return s.removeAll(c); }
public boolean retainAll(Collection c){ return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public Object[] toArray(Object[] a) { return s.toArray(a); }
public boolean equals(Object o) { return s.equals(o); }
public int hashCode() { return s.hashCode(); }
public String toString() { return s.toString(); }
}
Per settare l’interfaccia, in questo modo il costruttore accetta qualsiasi tipo di
implementazione.
Set s1 = new InstrumentedSet(new TreeSet(list));
Set s2 = new InstrumentedSet(new HashSet(capacity, loadFactor));
Si può anche wrap temporaneamente un set già esistente tramite static:
static void f(Set s) { // why is this static ? Is only about forwarding,
without added state
InstrumentedSet sInst = new InstrumentedSet(s);
... // Within this method use sInst instead of s
}
Il problema SELF: L’oggetto wrapped non sa di esserlo (wrapped).
Reminder:
In programmazione, un callback è, in genere, una funzione, o un "blocco di codice" che viene passata come parametro ad
un'altra funzione. In particolare, quando ci si riferisce alla callback richiamata da una funzione, la callback viene passata come
argomento ad un parametro della funzione chiamante. In questo modo la chiamante può realizzare un compito specifico
(quello svolto dalla callback) che non è, molto spesso, noto al momento della scrittura del codice.
8
In uno schema callback potrebbe passare un riferimento a se stessa eludendo il wrapper.
(slide poco chiare !)
In generale l’ereditarietà è appropriata solo se la sottoclasse è realmente un sottotipo
Se si usa l’ereditarietà al posto della composizione anche se
della superclasse.
questa è di per se già appropriata per il caso in questione, si deve necessariamente
fornire i dettagli implementativi.
15
ITEM crea documentazione per l’ereditarietà o altrimenti proibiscila
23 controllare i parametri passati
non riassunti (vedi slide se dovessero servire).
ITEM 16
Preferire interfacce rispetto a classi astratte.
Classi astratte e interfacce forniscono meccanismi per definire i tipi.
classi astratte possono implementare alcuni metodi
La differenza tra le 2 è che le
interfacce contengono solo dichiarazioni.
mentre le una classe
L'ereditarietà singola impone che deve discendere direttamente da una
può implementare sempre una o più
classe astratta o una sua sottoclasse mentre
interfacce in qualsiasi punto della gerarchia.
Le interfacce permettono di costruire dei framework di tipo non gerarchico e permettono
il wrapping.
Classi esistenti possono essere adattate facilmente per implementare una nuova
interfaccia, basta implementare tutti i metodi dell'interfaccia
→
Classi astratte skeletal implementation più evolvibili
La skeletal implementation è definita lasciando i metodi primitivi della classe non
implementati, quei metodi che saranno ridefiniti sicuramente nella sottoclasse e
implementando tutti gli altri. Se è necessario aggiungere un metodo alla classe astratta
si può fornire una implementazione di base.
ITEM 18
Favorire membri di classe statici piuttosto che non static.
Favorire classi con metodi statici.
Nested class: classe definita all'interno di un'altra classe. Deve servire solo all'interno
della classe in cui è definita.
4 tipi di classi classi nested:
static member classes:
1- quando non c'è da accedere a risorse contenute in una
istanza. Evita che ogni classe abbia al suo interno una istanza alla classe nested.
Funziona anche quando non c'è una istanza della classe che la contiene. Farla privata per
rappresentare componenti dell'oggetto della classe che la contiene.
9
nner classes
2- non static member classes usate per definire adapter e componenti che
i :
agiscono sulle istanze.
public class MySet exstends AbstractSet {
public new
Iteretor iterator() { MyIterator(); }
// non-static member class
private class implements
MyIterator Iterator {
// ....
}
}
anonymous classes:
3- quando c'è bisogno di function objects e quando non c'è
bisogno di richiamarle dopo che sono state istanziate. Vengono implementati i metodi
delle loro interfacce o superclassi. Piccole perchè scritte nel mezzo del codice, altimenti
diventano illeggibili.
// ....
Array.sort(args, new Comparator(){
public Object
int compare(Object o1, o2) {
return (String)o1.lenght() == (String)o1.lenght();
}
})
// ....
local classes:
4- usate di meno, classi dichiarate nei punti dove si dichiarano variabili
locali e subiscono le stesse regole. Come meber classes possiedono identificatori e
possono istanziate più volte.
Come le classi anonime devono essere compatte per non violare la leggibilità
ITEM 22
Rimpiazzare puntatori a funzioni con classi e interfacce.
C supporta puntatori a funzione usati come funzione per specializzare lo scopo di una
funzione ad un'altra. Qualche volta vengono chiamate callback. Simili allo strategy
pattern.
void qsort (void* base, size_t num, size_t size, int (*compar)
(const void*,const void*));
int compare (const void * a, const void * b) { return ( *(int*)a -
*(int*)b ); }
qsort (values, 6, sizeof(int), compare);
Java usa i function object per eseguire lo stesso scopo.
Function object: oggetto che esegue una operazione su un'altro oggetto. Sono stateless,
senza attributi e contenente solo metodi, per questo possono essere dei Singleton
class StrLenComp implements Comparator {
private StrLenComp() {} // Private constructor
public static final StrLenComp INSTANCE = new StrLenComp();
10
public int compare(String s1, String s2) {
return s1.lenght() - s2.lenght();
}
}
Le strategy class sono spesso dichiarate usando le anonymous class.
// ....
Array.sort(args, new Comparator(){
public int compare(Object o1, Object o2) {
return (String)o1.lenght() ==
(String)o1.lenght();
}
})
// ….
ITEM 23
Controllare i parametri passati
Molti metodi e costruttori hanno restrizioni su i parametri da passargli, valori non negativi,
no referenze a null, parametri in un certo intervallo…
E’ quindi buona norma controllare i valori ricevuti all’inizio di ogni metodo.
throw
e lanciare delle eccezioni tramite in caso di parametri sbagliati.
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0)
throw new ArithmeticException(“Modulus <=0:&rd