Progettare le Classi C. Horstmann Fondamenti di programmazione e Java 2 3^ edizione Apogeo trad. Nicola Fanizzi corso di Programmazione, CdS: Informatica TPS Dip. di Informatica, Università degli studi di Bari
Obiettivi 2 Imparare come scegliere le classi giuste da implementare Capire i concetti di coesione e accoppiamento Minimizzare l'uso degli effetti collaterali Documentare le responsabilità dei metodi e dei loro chiamanti con precondizioni e postcondizioni
Obiettivi 3 Capire la differenza tra metodi d'istanza e metodi statici Introdurre il concetto dei campi statici Capire le regole di visibilità per le variabili locali e i campi d'istanza Imparare l'uso dei pacchetti
Scegliere le Classi 4 Una classe rappresenta un singolo concetto del dominio del problema Il nome di una classe dovrebbe essere un sostantivo che descrive il concetto Concetti dalla matematica: Punto Rettangolo Ellissi Concetti dalla vita reale ContoCorrente RegistratoreDiCassa
Scegliere le Classi 5 Attori: terminano in -or, -er (Inglese) / -ore, -ere (Italiano); fanno per noi qualche tipo di lavoro Scanner Random -> nome migliore: RandomNumberGenerator Classi di Utilità: niente oggetti, solo metodi statici e costanti Math Programmi: hanno solo il metodo main Non trasformare le azioni in classi: PayCheck (busta paga) migliore di ComputePayCheck
Coesione 6 Una classe deve rappresentare un singolo concetto L'interfaccia pubblica di una classe è coesiva se tutte le sue caratteristiche sono relative al concetto che la classe rappresenta
Coesione 7 Questa classe manca di coesione: public class CashRegister { public void enterpayment (int dollars, int quarters, int dimes, int nickels, int pennies)... public static final double NICKEL_VALUE = 0.05; public static final double DIME_VALUE = 0.1; public static final double QUARTER_VALUE = 0.25;... }
Coesione 8 CashRegister,, come detto, riguarda due concetti: registratore di cassa e moneta Soluzione: Costruire due classi public class Coin { public Coin(double avalue, String aname){... } public double getvalue(){... }... } public class CashRegister { public void enterpayment (int coincount, Coin cointype) {... }... }
Accoppiamento 9 Una classe dipende da un'altra se usa oggetti di quella classe CashRegister dipende da Coin nel determinare l'entità del pagamento Coin non dipende da CashRegister Alto accoppiamento = molte dipendenze tra classi Minimizzare l'accoppiamento per minimizzare l'impatto dei cambi di interfaccia Per visualizzare le relazioni disegnare diagrammi di classe UML: Unified Modeling Language Notazione per l'analisi e la progettazione
Accoppiamento 10 UML: Relazioni di dipendenza tra le classi CashRegister e Coin
Alto e Basso Accoppiamento tra Classi 11 Figure 2 High and Low Coupling Between Classes
Accessori, Mutatori e Classi Immutabili Accessore: : non cambia lo stato del parametro implicito 12 double balance = account.getbalance(); Mutatore: : modifica l'oggetto sul quale è invocato account.deposit(1000);
Accessori, Mutatori e Classi Immutabili Classe Immutabile: : non ha metodi mutatori (es., String) 13 String name = "John Q. Public"; String uppercased = name.touppercase(); // il nome non è cambiato È più sicuro restituire riferimenti ad istanze di classi immutabili; non si può modificare l'oggetto in un momento inatteso
Effetti Collaterali 14 Effetto collaterale di un metodo: ogni modifica di dati osservabile esternamente public void transfer(double amount, BankAccount other) { balance = balance - amount; other.balance = other.balance + amount; // Modifica il parametro esplicito } Aggiornare un parametro esplicito può risultare qualcosa di inaspettato per altri programmatori meglio evitare, se possibile
Effetti Collaterali 15 Un altro esempio di effetto collaterale è l'output public void printbalance() // non raccomndabile { System.out.println("The balance is now $" + balance); } Cattiva idea: il messaggio è in inglese e si usa System.out Meglio disaccoppiare l'input/output dal lavoro reale delle classi Si deve minimizzare gli effetti collaterali che vanno oltre la modifica del parametro implicito
Errore Comune: Modificare un Parametro di Tipo Primitivo 16 void transfer(double amount, double otherbalance) { balance = balance - amount; otherbalance = otherbalance + amount; } Non funziona Scenario: double savingsbalance = 1000; harryschecking.transfer(500, savingsbalance); System.out.println(savingsBalance); In Java, un metodo non può modificare alcun parametro di tipo primitivo
Modifica Parametro Numerico Nessun Effetto sul Chiamante 17 Figure 3(1): Modifying a Numeric Parameter Has No Effect on Caller
Modifica Parametro Numerico Nessun Effetto sul Chiamante 18 Figure 3(2): Modifying a Numeric Parameter Has No Effect on Caller
Modifica Parametro Numerico Nessun Effetto sul Chiamante 19 Figure 3(3): Modifying a Numeric Parameter Has No Effect on Caller
Modifica Parametro Numerico Nessun Effetto sul Chiamante 20 Figure 3(4): Modifying a Numeric Parameter Has No Effect on Caller
Chiamata per Valore e per Riferimento Chiamata per valore: : i parametri del metodo sono copiati nelle variabili parametro quando il metodo comincia la sua esecuzione Chiamata per riferimento: : i metodi possono modificare direttamente i parametri Java ammette chiamate per valore 21
Chiamata per Valore e per Riferimento 22 Un metodo può cambiare lo stato di parametri che sono riferimenti ad oggetti, ma non può sostituire un riferimento ad oggetto con un altro public class BankAccount { public void transfer (double amount, BankAccount otheraccount) { balance = balance - amount; double newbalance = otheraccount.balance + amount; otheraccount = new BankAccount(newBalance); // non funziona } }
Esempio: Chiamata per Valore 23 harryschecking.transfer(500, savingsaccount); Figure 4: Modifying an Object Reference Parameter Has No Effect on the Caller
Precondizioni 24 Precondizione: : Vincolo che il chiamante di un metodo deve soddisfare Rendere pubbliche le precondizioni in modo che il chiamante non invocherà metodi passando parametri sbagliati /** Deposita denaro sul conto. @param amount la cifra da depositare (Precondizione: amount >= 0) */
Precondizioni 25 Uso Tipico: Porre restrizioni sui parametri di un metodo Richiedere che un metodo venga invocato quando l'oggetto è nello stato appropriato Se viene violata una precondizione, il metodo non è più responsabile del calcolo del corretto risultato. È libero di fare qualsiasi cosa
Precondizioni 26 Un metodo può lanciare una eccezione qualora una precondizione venga violata (cfr. Cap. 14) if (amount < 0) throw new IllegalArgumentException(); balance = balance + amount; Un metodo non è obbligato a testare la precondizione (il test potrebbe essere costoso) // se il saldo diventa negativo, è colpa del chiamante balance = balance + amount;
Precondizioni 27 Un metodo può fare il controllo di un'asserzione assert amount >= 0; balance = balance + amount; Per abilitare il controllo delle asserzioni: java -enableassertions MyProg Si possono disabilitare le asserzioni dopo aver testato il programma, in modo da farlo girare alla massima velocità
Precondizioni 28 Molti programmatori inesperti in tali casi fanno tornare tacitamente al chiamante if (amount < 0) return; // poco raccomandabile; // difficile da debuggare balance = balance + amount;
Sintassi 9.1: Asserzione 29 assert condition; Esempio: assert amount >= 0; Scopo: : asserire che una condizione è soddisfatta. Se il controllo è abilitato e la condizione è falsa, viene lanciato un errore di asserzione
Postcondizioni 30 Postcondizione: : Condizione che è vera dopo che un metodo è stato completato Se la chiamata del metodo soddisfa le precondizioni deve assicurare che le postcondizioni siano valide Ci sono due tipi di postcondizioni: Il valore da restituire venga calcolato correttamente L'oggetto è in un determinato stato dopo che la chiamata del metodo sia completata
Postcondizioni 31 /** Deposita denaro su questo conto. (Postcondizione: getbalance() >= 0) @param amount la cifra da depositare (Precondizione: amount >= 0) */ Non serve documentare postcodizioni ovvie che replichino la clausola @return
Postcondizioni 32 Formulare pre- e postcondizioni solo in termini dell'interfaccia della classe amount <= getbalance() // maniera giusta di dichiarare una postcondizione amount <= balance // formulazione sbagliata Contratto: : se il chiamante soddisfa le precondizioni, il metodo soddisferà la postcondizione
Metodi Statici 33 Ogni metodo deve trovarsi in una classe Un metodo statico non viene invocato su un oggetto Perché scrivere un metodo che non opera su un oggetto? Ragione generale: incapsulare calcoli che non riguardano solo numeri. I numeri non sono oggetti, non si possono invocare metodi su di essi Es: x.sqrt() non sarà mai lecito in Java
Metodi Statici 34 public class Financial { public static double percentof(double p, double a) { return (p / 100) * a; } // Si possono aggiungere altri metodi di matematica finanziaria } Chiamata su una classe invece di un oggetto: double tax = Financial.percentOf(taxRate, total); main è statico non ci sarebbero ancora oggetti su cui invocarlo
Campi static 35 Un campo static appartiene alla classe e non ad un qualunque oggetto della classe. Si chiama anche campo di classe. public class BankAccount {... private double balance; private int accountnumber; private static int lastassignednumber = 1000; } Se lastassignednumber non fosse static, ogni istanza di BankAccount avrebbe il suo valore di lastassignednumber
Campi static 36 public BankAccount() { // Genera il prossimo numero conto da assegnare lastassignednumber++; // Updates the static field // assegna il campo numero conto presso questa banca account accountnumber = lastassignednumber; // imposta il campo istanza } Minimizzare l'uso di campi statici (vanno bene campi statici che siano anche final)
Campi static 37 Tre modalità di inizializzazione: 1) Non far nulla. Il campo assume 0 (per numeri), false (valori booleani), o null (per oggetti) 2) Usare una esplicita inizializzazione, come public class BankAccount {... private static int lastassignednumber = 1000; // eseguto una sola volta, // al caricamento della classe } 3) Usare un blocco di inizializzazione statica
Campi static 38 3) Usare un blocco di inizializzazione (statico) public class BankAccount {... private static int lastassignednumber; } static { lastassignednumber = 1000; } NB: vale anche per inizializzare i campi d'istanza; in tal caso occorre omettere la parola chiave static
Campi static 39 I campi statici andrebbero dichiarati sempre come private Eccezione: : Costanti statiche, che potrebbero essere sia private sia public public class BankAccount {... public static final double OVERDRAFT_FEE = 5; // Vi si fa riferimento con // BankAccount.OVERDRAFT_FEE }
Campi di Classe e di Istanza 40
Visibilità delle Variabili Locali 41 Visibilità (scope)) di una variabile: regione del programma nella quale si può accedere alla variabile La visibilità di una variabile locale si estende dalla sua dichiarazione alla fine del blocco che la racchiude
Visibilità delle Variabili Locali 42 A volte si può usare lo stesso nome di variabile in due metodi. Tali variabili sono mutuamente indipendenti; le loro visibilità sono disgiunte
Visibilità delle Variabili Locali 43 public class RectangleTester { public static double area(rectangle rect) { double r = rect.getwidth() * rect.getheight(); return r; } public static void main(string[] args) { Rectangle r = new Rectangle(5, 10, 20, 30); double a = area(r); System.out.println(r); } }
Visibilità delle Variabili Locali 44 Lo scope di una variabile locale non può contenere la definizione di un'altra variabile con lo stesso nome Rectangle r = new Rectangle(5, 10, 20, 30); if (x >= 0) { double r = Math.sqrt(x); // Errore non puoi dichiarare qui un'altra var. chiamata r... }
Visibilità delle Variabili Locali 45 Tuttavia, ci possono essere variabioli locali con nomi identici se gli scope non si sovrappongono if (x >= 0) { double r = Math.sqrt(x);... } // lo scope di r finisce qui else { Rectangle r = new Rectangle(5, 10, 20, 30); // OK qui è lecito dichiarare un'altra r... }
Visibilità di Classe 46 I membri private hanno uno scope di classe: si può accedere a tutti i membri in qualunque metodo all'interno della classe Al di fuori della classe, occorre qualificare i membri pubblici Math.sqrt harryschecking.getbalance
Scope dei Membri della Classe 47 All'interno di un metodo non serve qualificare i campi e i metodi che appartengono alla data classe Un campo o metodo d'istanza non qualificatosi riferisce al parametro this public class BankAccount { public void transfer(double amount, BankAccount other) { withdraw(amount); // ovvero this.withdraw(amount); } }... other.deposit(amount);
Visibilità Sovrapposte 48 Una variabile locale può oscurare (shadow( shadow) ) un campo con lo stesso nome Lo scope locale vince su quello di classe public class Coin {... public double getexchangevalue(double exchangerate) { double value; // Variabile locale... return value; } private String name; private double value; // Campo con lo stesso nome }
Visibilità Sovrapposte 49 Si accede a campi oscurati qualificandoli con il riferimento this value = this.value * exchangerate;
Organizzare Classi Correlate in Package Package: insieme di classi correlate Per assegnare alcune classi in un package, si deve mettere una riga 50 package packagename; come prima istruzione del sorgente contenente le classi Il nome di un pacchetto consiste in uno o più identificatori separati da punti
Organizzare Classi Correlate in Package 51 Ad esempio, per mettere la classe Financial nel package com.horstmann.bigjava, il file Financial.java deve cominciare come segue: package com.horstmann.bigjava; public class Financial {... } Il package di default non ha nome nè serve qualificarlo con un'istruzione package
Organizzare Classi Correlate in Package 52 Package java.lang java.util java.io Java.awt Java.applet Java.net Java.sql Java.swing Org.omg.CORBA Scopo Supporto al linguaggio Utilità Input e Output Abstract Windowing Toolkit Applet Networking Accesso ai Database Swing user interface Common Object Request Broker Architecture Esempio Math Random PrintScreen Color Applet Socket ResultSet JButton IntHolder
package packagename; Sintassi 9.2: Specifica dei Package 53 Esempio: package it.uniba.di.fanizzi; Scopo: : per dichiarare che tutte le classi nel file presente appartiene ad un particolare package
Importare dai Package 54 Si può sempre usare una classe senza importarla java.util.scanner in = new java.util.scanner(system.in); Usare nomi qualificati è tedioso Usare import permette di usare nomi più corti import java.util.scanner;... Scanner in = new Scanner(System.in)
Importare dai Package 55 Si possono importare tutte le classi in un package import java.util.*; Non c'è mai bisogno di importare java.lang Non c'è bisogno di importare altre classi nello stesso package
I Nomi di Package e Localizzare le Classi Su usano i package per evitare collisioni di nomi 56 java.util.timer vs. javax.swing.timer I nomi di package dovrebbero essere non ambigui Raccomandazione: iniziare invertendo il nome del dominio com.horstmann.bigjava edu.sjsu.cs.walters: : per le classi di Bertha Walters (walters@cs.sjsu.edu( walters@cs.sjsu.edu)
I Nomi di Package e Localizzare le Classi 57 La stringa del path dovrebbe corrispondere al nome del package: com/horstmann/bigjava/financial.java Il nome del percorso inizia con il percorso della classe: export CLASSPATH=/home/walters/lib:. set CLASSPATH=c:\home\walters\lib;. CLASSPATH contiene le directory di base che possono contenere directory di package
Directory di Base e Sottodirectories per Package 58 Figure 6: Base Directories and Subdirectories for Packages
Importazione di Pacchetti static 59 (da Argomenti avanzati 9.4) A partire da Java 5.0, per agevolare la scrittura/lettura: import static import static java.lang.system.*; import static java.lang.math.*; public class RootTester { public static void main(string args[]) { double r = sqrt(pi); out.println( radice +r); } }