Anteprima
Vedrai una selezione di 4 pagine su 14
Appunti Ingegneria del software prof. Enrico Vicario Pag. 1 Appunti Ingegneria del software prof. Enrico Vicario Pag. 2
Anteprima di 4 pagg. su 14.
Scarica il documento per vederlo tutto.
Appunti Ingegneria del software prof. Enrico Vicario Pag. 6
Anteprima di 4 pagg. su 14.
Scarica il documento per vederlo tutto.
Appunti Ingegneria del software prof. Enrico Vicario Pag. 11
1 su 14
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

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

Dettagli
A.A. 2017-2018
14 pagine
2 download
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher tommasogiannoni di informazioni apprese con la frequenza delle lezioni di Ingegneria del software e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Firenze o del prof Vicario Enrico.