Progettare con membri statici

Sebbene Java sia in larga misura orientato agli oggetti, non è un puro linguaggio orientato agli oggetti. Uno dei motivi per cui Java non è puramente orientato agli oggetti è che non tutto in esso è un oggetto. Ad esempio, Java permette di dichiarare le variabili di tipi primitivi ( int, float, boolean, etc.), che non sono oggetti. E Java ha campi e metodi statici, che sono indipendenti e separati dagli oggetti. Questo articolo fornisce consigli su come utilizzare campi e metodi statici in un programma Java, mantenendo un focus orientato agli oggetti nei progetti.

La durata di una classe in una JVM (Java virtual machine) ha molte somiglianze con la durata di un oggetto. Proprio come un oggetto può avere uno stato, rappresentato dai valori delle sue variabili di istanza, una classe può avere uno stato, rappresentato dai valori delle sue variabili di classe. Proprio come la JVM imposta le variabili di istanza sui valori iniziali predefiniti prima di eseguire il codice di inizializzazione, la JVM imposta le variabili di classe sui valori iniziali predefiniti prima di eseguire il codice di inizializzazione. E come gli oggetti, le classi possono essere raccolte in Garbage Collection se non sono più referenziate dall'applicazione in esecuzione.

Tuttavia, esistono differenze significative tra classi e oggetti. Forse la differenza più importante è il modo in cui vengono richiamati i metodi di istanza e di classe: i metodi di istanza sono (per la maggior parte) legati dinamicamente, ma i metodi di classe sono vincolati staticamente. (In tre casi speciali, i metodi di istanza non sono associati dinamicamente: invocazione di metodi di istanza privati, invocazione di initmetodi (costruttori) e invocazioni con la superparola chiave. Vedere Risorse per ulteriori informazioni.)

Un'altra differenza tra classi e oggetti è il grado di occultamento dei dati garantito dai livelli di accesso privato. Se una variabile di istanza viene dichiarata privata, solo i metodi di istanza possono accedervi. Ciò consente di garantire l'integrità dei dati dell'istanza e rendere gli oggetti thread-safe. Il resto del programma non può accedere direttamente a quelle variabili di istanza, ma deve passare attraverso i metodi di istanza per manipolare le variabili di istanza. Nel tentativo di fare in modo che una classe si comporti come un oggetto ben progettato, è possibile rendere private le variabili di classe e definire metodi di classe che le manipolino. Tuttavia, in questo modo non si ottiene una buona garanzia di thread safety o persino di integrità dei dati, perché un certo tipo di codice ha un privilegio speciale che dà loro accesso diretto alle variabili di classe private: metodi di istanza,e anche gli inizializzatori di variabili di istanza possono accedere direttamente a quelle variabili di classe privata.

Quindi i campi statici e i metodi delle classi, sebbene simili in molti modi ai campi delle istanze e ai metodi degli oggetti, presentano differenze significative che dovrebbero influenzare il modo in cui vengono utilizzati nei progetti.

Trattare le classi come oggetti

Durante la progettazione di programmi Java, probabilmente incontrerai molte situazioni in cui senti il ​​bisogno di un oggetto che agisca in qualche modo come una classe. Ad esempio, potresti volere un oggetto la cui durata corrisponde a quella di una classe. Oppure potresti volere un oggetto che, come una classe, si limiti a una singola istanza in un dato spazio dei nomi.

In situazioni di progettazione come queste, si può essere tentati di creare una classe e usarla come un oggetto per definire variabili di classe, renderle private e definire alcuni metodi di classe pubblici che manipolano le variabili di classe. Come un oggetto, una tale classe ha uno stato. Come un oggetto ben progettato, le variabili che definiscono lo stato sono private e il mondo esterno può influenzare questo stato solo invocando i metodi di classe.

Sfortunatamente, esistono alcuni problemi con questo approccio "classe come oggetto". Poiché i metodi di classe sono vincolati staticamente, la tua classe come oggetto non godrà dei vantaggi di flessibilità del polimorfismo e dell'upcasting. (Per le definizioni di polimorfismo e associazione dinamica, vedere l'articolo Tecniche di progettazione, Composizione contro ereditarietà.) Il polimorfismo è reso possibile, e l'upcasting utile, dall'associazione dinamica, ma i metodi di classe non sono vincolati dinamicamente. Se qualcuno crea una sottoclasse della tua classe come oggetto, non sarà in grado di sovrascrivere i metodi della tua classe dichiarando metodi di classe con lo stesso nome; potranno solo nascondersiloro. Quando viene richiamato uno di questi metodi di classe ridefiniti, la JVM selezionerà l'implementazione del metodo da eseguire non dalla classe di un oggetto in fase di runtime, ma dal tipo di una variabile in fase di compilazione.

Inoltre, la sicurezza dei thread e l'integrità dei dati ottenute dalla meticolosa implementazione dei metodi di classe nella classe come oggetto è come una casa costruita con la paglia. La sicurezza del thread e l'integrità dei dati saranno garantite finché tutti utilizzeranno i metodi di classe per manipolare lo stato memorizzato nelle variabili di classe. Ma un programmatore incurante o incapace potrebbe, con l'aggiunta di un metodo di istanza che accede direttamente alle variabili della tua classe privata, sbuffare inavvertitamente e soffiare via la sicurezza del thread e l'integrità dei dati.

Per questo motivo, la mia linea guida principale riguardo alle variabili di classe e ai metodi di classe è:

Non trattare le classi come oggetti.

In altre parole, non progettare con campi e metodi statici di una classe come se fossero i campi e i metodi di istanza di un oggetto.

Se desideri uno stato e un comportamento la cui durata corrisponda a quella di una classe, evita di utilizzare variabili di classe e metodi di classe per simulare un oggetto. Invece, creare un oggetto reale e utilizzare una variabile di classe per contenere un riferimento ad esso e metodi di classe per fornire l'accesso al riferimento all'oggetto. Se vuoi assicurarti che esista solo un'istanza di uno stato e un comportamento in un singolo spazio dei nomi, non provare a progettare una classe che simuli un oggetto. Crea invece un singleton , un oggetto garantito per avere una sola istanza per spazio dei nomi.

Allora a cosa servono i membri della classe?

Secondo me, la migliore mentalità da coltivare quando si progettano programmi Java è pensare a oggetti, oggetti, oggetti. Concentrati sulla progettazione di grandi oggetti e pensa alle classi principalmente come schemi per oggetti: la struttura in cui definisci le variabili di istanza ei metodi di istanza che compongono i tuoi oggetti ben progettati. Oltre a ciò, puoi pensare che le classi forniscano alcuni servizi speciali che gli oggetti non possono fornire o non possono fornire in modo altrettanto elegante. Pensa alle lezioni come:

  • il posto giusto per definire i "metodi di utilità" (metodi che accettano input e forniscono output solo attraverso i parametri passati e il valore restituito)
  • un modo per controllare l'accesso a oggetti e dati

Metodi di utilità

Metodi che non manipolano o utilizzano lo stato di un oggetto o di una classe io chiamo "metodi di utilità". I metodi di utilità restituiscono semplicemente un valore (o valori) calcolato esclusivamente dai dati passati al metodo come parametri. È necessario rendere statici tali metodi e inserirli nella classe più strettamente correlata al servizio fornito dal metodo.

Un esempio di un metodo di utilità è il String copyValueOf(char[] data)metodo di classe String. Questo metodo produce il suo output, un valore restituito di tipo String, esclusivamente dal suo parametro di input, un array di chars. Poiché copyValueOf()né utilizza né influisce sullo stato di alcun oggetto o classe, è un metodo di utilità. E, come dovrebbero essere tutti i metodi di utilità, copyValueOf()è un metodo di classe.

Quindi uno dei modi principali per utilizzare i metodi di classe è come metodi di utilità: metodi che restituiscono un output calcolato esclusivamente dai parametri di input. Altri usi dei metodi di classe coinvolgono le variabili di classe.

Variabili di classe per nascondere i dati

Uno dei precetti fondamentali nella programmazione orientata agli oggetti è l'occultamento dei dati , che limita l'accesso ai dati per ridurre al minimo le dipendenze tra le parti di un programma. Se un particolare pezzo di dati ha un'accessibilità limitata, quei dati possono cambiare senza interrompere quelle parti del programma che non possono accedere ai dati.

Se, ad esempio, un oggetto è necessario solo per istanze di una particolare classe, un riferimento ad esso può essere memorizzato in una variabile di classe privata. Questo dà a tutte le istanze di questa classe un comodo accesso a quell'oggetto - le istanze lo usano direttamente - ma nessun altro codice da nessun'altra parte del programma può raggiungerlo. In modo simile, è possibile utilizzare l'accesso al pacchetto e le variabili di classe protette per ridurre la visibilità degli oggetti che devono essere condivisi da tutti i membri di un pacchetto e delle sottoclassi.

Le variabili di classe pubblica sono una storia diversa. Se una variabile di classe pubblica non è definitiva, è una variabile globale: quel brutto costrutto che è l'antitesi del nascondere i dati. Non ci sono mai scuse per una variabile di classe pubblica, a meno che non sia definitiva.

Le variabili finali della classe pubblica, sia di tipo primitivo che di riferimento a oggetti, hanno uno scopo utile. Le variabili di tipo primitivo o di tipo Stringsono semplicemente costanti, che in generale aiutano a rendere i programmi più flessibili (più facili da modificare). Il codice che utilizza le costanti è più facile da modificare perché è possibile modificare il valore della costante in un unico punto. Le variabili di classe finale pubbliche dei tipi di riferimento consentono di fornire l'accesso globale agli oggetti necessari a livello globale. Ad esempio, System.in, System.oute System.errsono variabili public final class che danno accesso globale ai flussi in uscita di ingresso e di errore standard.

Quindi il modo principale per visualizzare le variabili di classe è come un meccanismo per limitare l'accessibilità di (significato, per nascondere) variabili o oggetti. Quando si combinano metodi di classe con variabili di classe, è possibile implementare politiche di accesso ancora più complicate.

Utilizzo di metodi di classe con variabili di classe

Oltre a fungere da metodi di utilità, i metodi di classe possono essere utilizzati per controllare l'accesso agli oggetti memorizzati nelle variabili di classe, in particolare per controllare come gli oggetti vengono creati o gestiti. Due esempi di questo tipo di metodo di classe sono i metodi setSecurityManager()e getSecurityManager()di classe System. Il gestore della sicurezza per un'applicazione è un oggetto che, come i flussi di input, output e errore standard, è necessario in molti luoghi diversi. A differenza degli oggetti flusso di I / O standard, tuttavia, un riferimento al gestore della sicurezza non viene memorizzato in una variabile di classe finale pubblica. L'oggetto del gestore della sicurezza è memorizzato in una variabile di classe privata ei metodi set e get implementano una politica di accesso speciale per l'oggetto.

Il modello di sicurezza di Java pone una restrizione speciale sul gestore della sicurezza. Prima di Java 2 (precedentemente noto come JDK 1.2), un'applicazione iniziava la sua vita senza alcun gestore della sicurezza ( getSecurityManager()restituito null). La prima chiamata per setSecurityManager()stabilire il responsabile della sicurezza, che in seguito non è stato autorizzato a cambiare. Qualsiasi chiamata successiva a setSecurityManager()darebbe luogo a un'eccezione di sicurezza. In Java 2, l'applicazione inizia sempre con un gestore della sicurezza, ma simile alle versioni precedenti, il setSecurityManager()metodo ti permetterà di cambiare il gestore della sicurezza una volta, al massimo.

Il gestore della sicurezza fornisce un buon esempio di come i metodi di classe possono essere utilizzati insieme alle variabili di classe private per implementare una politica di accesso speciale per gli oggetti a cui fanno riferimento le variabili di classe. A parte i metodi di utilità, pensa ai metodi di classe come ai mezzi per stabilire politiche di accesso speciali per riferimenti a oggetti e dati memorizzati nelle variabili di classe.

Linee guida

Il punto principale del consiglio fornito in questo articolo è:

Non trattare le classi come oggetti.

Se hai bisogno di un oggetto, crea un oggetto. Limitare l'uso di variabili e metodi di classe alla definizione di metodi di utilità e all'implementazione di tipi speciali di politiche di accesso per oggetti e tipi primitivi memorizzati nelle variabili di classe. Sebbene non sia un puro linguaggio orientato agli oggetti, Java è comunque orientato agli oggetti in larga misura e i tuoi progetti dovrebbero riflettere questo. Pensa agli oggetti.

Il prossimo mese

L' articolo Design Techniques del prossimo mese sarà l'ultimo di questa colonna. Presto inizierò a scrivere un libro basato sul materiale di Design Techniques, Flexible Java , e inserirò quel materiale sul mio sito web man mano che procederò. Quindi per favore segui quel progetto e inviami un feedback. Dopo una pausa di un mese o due, tornerò a JavaWorld e SunWorld con una nuova colonna incentrata su Jini.

Una richiesta di partecipazione del lettore

Incoraggio i vostri commenti, critiche, suggerimenti, accenti - ogni tipo di feedback - sul materiale presentato in questa colonna. Se non sei d'accordo con qualcosa o hai qualcosa da aggiungere, fammelo sapere.

Puoi partecipare a un forum di discussione dedicato a questo materiale, inserire un commento tramite il modulo in fondo all'articolo, o inviarmi un'e-mail direttamente utilizzando il link fornito nella mia biografia qui sotto.

Bill Venners scrive software professionalmente da 12 anni. Con sede nella Silicon Valley, fornisce consulenza sul software e servizi di formazione sotto il nome di Artima Software Company. Negli anni ha sviluppato software per i settori dell'elettronica di consumo, dell'istruzione, dei semiconduttori e delle assicurazioni sulla vita. Ha programmato in molti linguaggi su molte piattaforme: linguaggio assembly su vari microprocessori, C su Unix, C ++ su Windows, Java sul Web. È autore del libro Inside the Java Virtual Machine, pubblicato da McGraw-Hill.