JavaRush /Java Blog /Random-IT /Progettare classi e interfacce (Traduzione dell'articolo)...
fatesha
Livello 22

Progettare classi e interfacce (Traduzione dell'articolo)

Pubblicato nel gruppo Random-IT
Progettare classi e interfacce (Traduzione dell'articolo) - 1

Contenuto

  1. introduzione
  2. Interfacce
  3. Marcatori di interfaccia
  4. Interfacce funzionali, metodi statici e metodi predefiniti
  5. Classi astratte
  6. Classi immutabili (permanenti).
  7. Classi anonime
  8. Visibilità
  9. Eredità
  10. Eredità multipla
  11. Ereditarietà e composizione
  12. Incapsulamento
  13. Classi e metodi finali
  14. Qual è il prossimo
  15. Scarica il codice sorgente

1. INTRODUZIONE

Indipendentemente dal linguaggio di programmazione utilizzato (e Java non fa eccezione), seguire i principi di buona progettazione è la chiave per scrivere un codice pulito, comprensibile e verificabile; e anche crearlo per essere di lunga durata e supportare facilmente la risoluzione dei problemi. In questa parte del tutorial discuteremo gli elementi costitutivi fondamentali forniti dal linguaggio Java e introdurremo un paio di principi di progettazione nel tentativo di aiutarti a prendere decisioni di progettazione migliori. Più specificamente, discuteremo di interfacce e interfacce che utilizzano metodi predefiniti (una nuova funzionalità in Java 8), classi astratte e finali, classi immutabili, ereditarietà, composizione e rivisiteremo le regole di visibilità (o accessibilità) a cui abbiamo brevemente accennato in Parte 1 lezione "Come creare e distruggere oggetti" .

2. INTERFACCE

Nella programmazione orientata agli oggetti il ​​concetto di interfaccia costituisce la base per lo sviluppo dei contratti . In poche parole, le interfacce definiscono un insieme di metodi (contratti) e ogni classe che richiede supporto per quella specifica interfaccia deve fornire un'implementazione di tali metodi: un'idea abbastanza semplice ma potente. Molti linguaggi di programmazione hanno interfacce in una forma o nell'altra, ma Java in particolare fornisce il supporto linguistico per questo. Diamo un'occhiata a una semplice definizione di interfaccia in Java.
package com.javacodegeeks.advanced.design;

public interface SimpleInterface {
void performAction();
}
Nello snippet sopra, l'interfaccia che abbiamo chiamato SimpleInterface, dichiara solo un metodo chiamato performAction. La differenza principale tra interfacce e classi è che le interfacce delineano ciò che dovrebbe essere il contatto (dichiarano un metodo), ma non ne forniscono l'implementazione. Tuttavia, le interfacce in Java possono essere più complesse: possono includere interfacce annidate, classi, conteggi, annotazioni e costanti. Per esempio:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefinitions {
    String CONSTANT = "CONSTANT";

    enum InnerEnum {
        E1, E2;
    }

    class InnerClass {
    }

    interface InnerInterface {
        void performInnerAction();
    }

    void performAction();
}
In questo esempio più complesso, ci sono diverse restrizioni che le interfacce impongono incondizionatamente sui costrutti annidati e sulle dichiarazioni dei metodi, e queste vengono applicate dal compilatore Java. Innanzitutto, anche se non dichiarata esplicitamente, ogni dichiarazione di metodo in un'interfaccia è pubblica (e può essere solo pubblica). Pertanto le seguenti dichiarazioni di metodo sono equivalenti:
public void performAction();
void performAction();
Vale la pena ricordare che ogni singolo metodo in un'interfaccia è implicitamente dichiarato abstract e anche queste dichiarazioni di metodo sono equivalenti:
public abstract void performAction();
public void performAction();
void performAction();
Per quanto riguarda i campi costanti dichiarati, oltre ad essere public , sono anche implicitamente statici e contrassegnati come final . Pertanto sono equivalenti anche le seguenti dichiarazioni:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
Infine, le classi, le interfacce o i conteggi nidificati, oltre ad essere public , sono anche dichiarati implicitamente static . Ad esempio, queste dichiarazioni equivalgono anche a:
class InnerClass {
}

static class InnerClass {
}
Lo stile che scegli è una preferenza personale, ma conoscere queste semplici proprietà delle interfacce può salvarti dalla digitazione non necessaria.

3. Indicatore di interfaccia

Un'interfaccia marcatore è un tipo speciale di interfaccia che non dispone di metodi o altri costrutti nidificati. Come lo definisce la libreria Java:
public interface Cloneable {
}
I marcatori di interfaccia non sono contratti di per sé, ma sono una tecnica alquanto utile per "attaccare" o "associare" alcuni tratti specifici con una classe. Ad esempio, rispetto a Cloneable , la classe è contrassegnata come clonabile, ma il modo in cui ciò può o deve essere implementato non fa parte dell'interfaccia. Un altro esempio molto famoso e ampiamente utilizzato di indicatore di interfaccia è Serializable:
public interface Serializable {
}
Questa interfaccia contrassegna la classe come adatta alla serializzazione e alla deserializzazione e, ancora una volta, non specifica come ciò possa o debba essere implementato. I marcatori di interfaccia hanno il loro posto nella programmazione orientata agli oggetti, sebbene non soddisfino lo scopo principale di un'interfaccia di essere un contratto. 

4. INTERFACCE FUNZIONALI, METODI DI DEFAULT E METODI STATICI

Dal rilascio di Java 8, le interfacce hanno acquisito alcune nuove funzionalità molto interessanti: metodi statici, metodi predefiniti e conversione automatica da lambda (interfacce funzionali). Nella sezione delle interfacce abbiamo sottolineato il fatto che le interfacce in Java possono solo dichiarare metodi, ma non fornirne l'implementazione. Con un metodo predefinito, le cose sono diverse: un'interfaccia può contrassegnare un metodo con la parola chiave predefinita e fornirne un'implementazione. Per esempio:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefaultMethods {
    void performAction();

    default void performDefaulAction() {
        // Implementation here
    }
}
Essendo a livello di istanza, i metodi predefiniti potrebbero essere sovrascritti da ciascuna implementazione dell'interfaccia, ma le interfacce ora possono includere anche metodi statici , ad esempio: pacchetto com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
    static void createAction() {
        // Implementation here
    }
}
Si potrebbe dire che fornire l'implementazione nell'interfaccia vanifica l'intero scopo della programmazione del contratto. Ma ci sono molte ragioni per cui queste funzionalità sono state introdotte nel linguaggio Java e non importa quanto siano utili o confuse, sono lì per te e per il tuo utilizzo. Le interfacce funzionali sono una storia completamente diversa e hanno dimostrato di essere aggiunte molto utili al linguaggio. Fondamentalmente, un'interfaccia funzionale è un'interfaccia con un solo metodo astratto dichiarato su di essa. RunnableL'interfaccia della libreria standard è un ottimo esempio di questo concetto.
@FunctionalInterface
public interface Runnable {
    void run();
}
Il compilatore Java tratta le interfacce funzionali in modo diverso e può trasformare una funzione lambda in un'implementazione dell'interfaccia funzionale dove ha senso. Consideriamo la seguente descrizione della funzione: 
public void runMe( final Runnable r ) {
    r.run();
}
Per chiamare questa funzione in Java 7 e versioni precedenti è necessario fornire un'implementazione dell'interfaccia Runnable(ad esempio utilizzando classi anonime), ma in Java 8 è sufficiente fornire un'implementazione del metodo run() utilizzando la sintassi lambda:
runMe( () -> System.out.println( "Run!" ) );
Inoltre, l' annotazione @FunctionalInterface (le annotazioni saranno trattate in dettaglio nella Parte 5 del tutorial) suggerisce che il compilatore può verificare se un'interfaccia contiene solo un metodo astratto, quindi qualsiasi modifica apportata all'interfaccia in futuro non violerà questo presupposto .

5. CLASSI ASTRATTE

Un altro concetto interessante supportato dal linguaggio Java è il concetto di classi astratte. Le classi astratte sono in qualche modo simili alle interfacce in Java 7 e sono molto vicine all'interfaccia del metodo predefinito in Java 8. A differenza delle classi normali, una classe astratta non può essere istanziata, ma può essere sottoclassata (fare riferimento alla sezione Ereditarietà per maggiori dettagli). Ancora più importante, le classi astratte possono contenere metodi astratti: un tipo speciale di metodo senza implementazione, proprio come un'interfaccia. Per esempio:
package com.javacodegeeks.advanced.design;

public abstract class SimpleAbstractClass {
    public void performAction() {
        // Implementation here
    }

    public abstract void performAnotherAction();
}
In questo esempio, la classe SimpleAbstractClassè dichiarata astratta e contiene un metodo astratto dichiarato. Le classi astratte sono molto utili; la maggior parte o anche alcune parti dei dettagli di implementazione possono essere condivise tra molte sottoclassi. Comunque sia, lasciano comunque la porta socchiusa e consentono di personalizzare il comportamento inerente a ciascuna sottoclasse utilizzando metodi astratti. Vale la pena ricordare che a differenza delle interfacce, che possono contenere solo dichiarazioni pubbliche , le classi astratte possono utilizzare tutta la potenza delle regole di accessibilità per controllare la visibilità di un metodo astratto.

6. LEZIONI IMMEDIATE

Al giorno d'oggi l'immutabilità sta diventando sempre più importante nello sviluppo del software. L'ascesa dei sistemi multi-core ha sollevato molte questioni relative alla condivisione e al parallelismo dei dati. Ma è sicuramente sorto un problema: avere pochi stati mutabili (o addirittura nessuno) porta a una migliore estensibilità (scalabilità) e a un ragionamento più semplice sul sistema. Sfortunatamente, il linguaggio Java non fornisce un supporto decente per l'immutabilità delle classi. Tuttavia, utilizzando una combinazione di tecniche, diventa possibile progettare classi immutabili. Innanzitutto tutti i campi della classe devono essere final (contrassegnati come final ). Questo è un buon inizio, ma non è una garanzia. 
package com.javacodegeeks.advanced.design;

import java.util.Collection;

public class ImmutableClass {
    private final long id;
    private final String[] arrayOfStrings;
    private final Collection<String> collectionOfString;
}
In secondo luogo, assicurati che l'inizializzazione sia corretta: se un campo è un riferimento a una raccolta o a un array, non assegnare tali campi direttamente dagli argomenti del costruttore, crea invece delle copie. Ciò garantirà che lo stato della raccolta o dell'array non venga modificato al di fuori di esso.
public ImmutableClass( final long id, final String[] arrayOfStrings,
        final Collection<String> collectionOfString) {
    this.id = id;
    this.arrayOfStrings = Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
    this.collectionOfString = new ArrayList<>( collectionOfString );
}
E infine, garantire un accesso adeguato (getter). Per le raccolte, l'immutabilità deve essere fornita come wrapper  Collections.unmodifiableXxx: con gli array, l'unico modo per fornire una vera immutabilità è fornire una copia invece di restituire un riferimento all'array. Ciò potrebbe non essere accettabile da un punto di vista pratico, poiché dipende molto dalla dimensione dell'array e può esercitare un'enorme pressione sul garbage collector.
public String[] getArrayOfStrings() {
    return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
}
Anche questo piccolo esempio dà una buona idea del fatto che l'immutabilità non è ancora una cittadina di prima classe in Java. Le cose possono complicarsi se una classe immutabile ha un campo che fa riferimento a un oggetto di un'altra classe. Anche queste classi dovrebbero essere immutabili, ma non c'è modo di garantirlo. Esistono diversi analizzatori di codice sorgente Java decenti, come FindBugs e PMD, che possono essere di grande aiuto controllando il codice e segnalando i difetti comuni di programmazione Java. Questi strumenti sono grandi amici di qualsiasi sviluppatore Java.

7. CLASSI ANONIME

Nell'era precedente a Java 8, le classi anonime erano l'unico modo per garantire che le classi fossero definite al volo e istanziate immediatamente. Lo scopo delle classi anonime era ridurre i tempi standard e fornire un modo breve e semplice per rappresentare le classi come record. Diamo un'occhiata al tipico modo vecchio stile per generare un nuovo thread in Java:
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread(
            // Example of creating anonymous class which implements
            // Runnable interface
            new Runnable() {
                @Override
                public void run() {
                    // Implementation here
                }
            }
        ).start();
    }
}
In questo esempio, l'implementazione dell'interfaccia Runnableviene fornita immediatamente come classe anonima. Sebbene esistano alcune limitazioni associate alle classi anonime, i principali svantaggi del loro utilizzo sono la sintassi di costruzione altamente dettagliata a cui Java come linguaggio è obbligato. Anche solo una classe anonima che non fa nulla richiede almeno 5 righe di codice ogni volta che viene scritta.
new Runnable() {
   @Override
   public void run() {
   }
}
Fortunatamente, con Java 8, lambda e le interfacce funzionali, tutti questi stereotipi scompariranno presto e finalmente scrivere il codice Java sembrerà davvero conciso.
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread( () -> { /* Implementation here */ } ).start();
    }
}

8. VISIBILITÀ

Abbiamo già parlato un po' delle regole di visibilità e accessibilità in Java nella Parte 1 del tutorial. In questa parte rivisiteremo nuovamente questo argomento, ma nel contesto delle sottoclassi. Progettare classi e interfacce (Traduzione dell'articolo) - 2La visibilità a diversi livelli consente o impedisce alle classi di vedere altre classi o interfacce (ad esempio, se si trovano in pacchetti diversi o annidate l'una nell'altra) o alle sottoclassi di vedere e accedere ai metodi, ai costruttori e ai campi dei loro genitori. Nella prossima sezione, l'ereditarietà, lo vedremo in azione.

9. EREDITÀ

L'ereditarietà è uno dei concetti chiave della programmazione orientata agli oggetti e funge da base per la costruzione di una classe di relazioni. Combinata con le regole di visibilità e accessibilità, l'ereditarietà consente di progettare le classi in una gerarchia che può essere estesa e mantenuta. A livello concettuale, l'ereditarietà in Java viene implementata utilizzando la sottoclasse e la parola chiave extends , insieme alla classe genitore. Una sottoclasse eredita tutti gli elementi pubblici e protetti della classe genitore. Inoltre, una sottoclasse eredita gli elementi privati ​​del pacchetto della sua classe genitore se entrambi (sottoclasse e classe) si trovano nello stesso pacchetto. Detto questo, è molto importante, indipendentemente da ciò che si sta tentando di progettare, attenersi all'insieme minimo di metodi che una classe espone pubblicamente o alle sue sottoclassi. Ad esempio, diamo un'occhiata alla classe Parente alla sua sottoclasse Childper dimostrare la differenza nei livelli di visibilità e nei loro effetti.
package com.javacodegeeks.advanced.design;

public class Parent {
    // Everyone can see it
    public static final String CONSTANT = "Constant";

    // No one can access it
    private String privateField;
    // Only subclasses can access it
    protected String protectedField;

    // No one can see it
    private class PrivateClass {
    }

    // Only visible to subclasses
    protected interface ProtectedInterface {
    }

    // Everyone can call it
    public void publicAction() {
    }

    // Only subclass can call it
    protected void protectedAction() {
    }

    // No one can call it
    private void privateAction() {
    }

    // Only subclasses in the same package can call it
    void packageAction() {
    }
}
package com.javacodegeeks.advanced.design;

// Resides in the same package as parent class
public class Child extends Parent implements Parent.ProtectedInterface {
    @Override
    protected void protectedAction() {
        // Calls parent's method implementation
        super.protectedAction();
    }

    @Override
    void packageAction() {
        // Do nothing, no call to parent's method implementation
    }

    public void childAction() {
        this.protectedField = "value";
    }
}
L'ereditarietà è di per sé un argomento molto ampio, con molti dettagli specifici specifici di Java. Tuttavia, esistono alcune regole facili da seguire e che possono contribuire notevolmente a mantenere la brevità della gerarchia delle classi. In Java, ciascuna sottoclasse può sovrascrivere qualsiasi metodo ereditato dal genitore a meno che non sia stata dichiarata finale. Tuttavia, non esiste una sintassi o una parola chiave speciale per contrassegnare un metodo come sovrascritto, il che può creare confusione. Questo è il motivo per cui è stata introdotta l'annotazione @Override : ogni volta che il tuo obiettivo è sovrascrivere un metodo ereditato, utilizza l' annotazione @Override per indicarlo succintamente. Un altro dilemma che gli sviluppatori Java affrontano costantemente nella progettazione è la costruzione di gerarchie di classi (con classi concrete o astratte) rispetto all'implementazione delle interfacce. Raccomandiamo fortemente di privilegiare le interfacce rispetto alle classi o alle classi astratte quando possibile. Le interfacce sono più leggere, più facili da testare e mantenere e riducono al minimo gli effetti collaterali delle modifiche all'implementazione. Molte tecniche di programmazione avanzate, come la creazione di classi proxy nella libreria standard Java, fanno molto affidamento sulle interfacce.

10. EREDITÀ MULTIPLA

A differenza del C++ e di alcuni altri linguaggi, Java non supporta l'ereditarietà multipla: in Java ogni classe può avere un solo genitore diretto (con la classe Objectal vertice della gerarchia). Tuttavia, una classe può implementare più interfacce e quindi l'impilamento delle interfacce è l'unico modo per ottenere (o simulare) l'ereditarietà multipla in Java.
package com.javacodegeeks.advanced.design;

public class MultipleInterfaces implements Runnable, AutoCloseable {
    @Override
    public void run() {
        // Some implementation here
    }

    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
L'implementazione di più interfacce è in realtà piuttosto potente, ma spesso la necessità di utilizzare l'implementazione più e più volte porta a una profonda gerarchia di classi come un modo per superare la mancanza di supporto di Java per l'ereditarietà multipla. 
public class A implements Runnable {
    @Override
    public void run() {
        // Some implementation here
    }
}
// Class B wants to inherit the implementation of run() method from class A.
public class B extends A implements AutoCloseable {
    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
// Class C wants to inherit the implementation of run() method from class A
// and the implementation of close() method from class B.
public class C extends B implements Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
E così via... La recente versione di Java 8 risolve in qualche modo il problema con l'iniezione del metodo predefinito. A causa dei metodi predefiniti, le interfacce in realtà forniscono non solo un contratto, ma anche un'implementazione. Pertanto, le classi che implementano queste interfacce erediteranno automaticamente anche questi metodi implementati. Per esempio:
package com.javacodegeeks.advanced.design;

public interface DefaultMethods extends Runnable, AutoCloseable {
    @Override
    default void run() {
        // Some implementation here
    }

    @Override
    default void close() throws Exception {
       // Some implementation here
    }
}

// Class C inherits the implementation of run() and close() methods from the
// DefaultMethods interface.
public class C implements DefaultMethods, Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
Tieni presente che l'ereditarietà multipla è uno strumento molto potente, ma allo stesso tempo pericoloso. Il noto problema Diamond of Death viene spesso citato come uno dei principali difetti nell'implementazione dell'ereditarietà multipla, costringendo gli sviluppatori a progettare le gerarchie di classi con molta attenzione. Sfortunatamente, anche le interfacce Java 8 con metodi predefiniti sono vittime di questi difetti.
interface A {
    default void performAction() {
    }
}

interface B extends A {
    @Override
    default void performAction() {
    }
}

interface C extends A {
    @Override
    default void performAction() {
    }
}
Ad esempio, il seguente frammento di codice non verrà compilato:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
A questo punto, è giusto dire che Java come linguaggio ha sempre cercato di evitare i casi limite della programmazione orientata agli oggetti, ma con l'evolversi del linguaggio, alcuni di questi casi hanno improvvisamente cominciato ad apparire. 

11. EREDITÀ E COMPOSIZIONE

Fortunatamente, l'ereditarietà non è l'unico modo per progettare la classe. Un'altra alternativa che molti sviluppatori ritengono sia molto migliore dell'ereditarietà è la composizione. L'idea è molto semplice: invece di creare una gerarchia di classi, queste devono essere composte da altre classi. Diamo un'occhiata a questo esempio:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
La classe Vehicleè composta da un motore e ruote (più molte altre parti che vengono lasciate da parte per semplicità). Tuttavia si può dire che una classe Vehicleè anche un motore, quindi può essere progettata utilizzando l'ereditarietà. 
public class Vehicle extends Engine {
    private Wheels[] wheels;
    // ...
}
Quale soluzione progettuale sarebbe corretta? Le linee guida generali fondamentali sono note come principi IS-A (è) e HAS-A (contiene). IS-A è una relazione di ereditarietà: una sottoclasse soddisfa anche la specifica della classe della classe genitore e una variazione della classe genitore. sottoclasse) estende il suo genitore. Se vuoi sapere se un'entità ne estende un'altra, esegui un test di corrispondenza - IS -A (è).") Pertanto, HAS-A è una relazione di composizione: una classe possiede (o contiene) un oggetto che Nella maggior parte dei casi, il principio HAS-A funziona meglio di IS-A per una serie di motivi: 
  • Il design è più flessibile;
  • Il modello è più stabile perché il cambiamento non si propaga attraverso la gerarchia delle classi;
  • Una classe e la sua composizione sono poco accoppiate rispetto alla composizione, che accoppia strettamente un genitore e la sua sottoclasse.
  • Il corso logico del pensiero in una classe è più semplice, poiché tutte le sue dipendenze sono incluse in essa, in un unico posto. 
In ogni caso, l'ereditarietà ha la sua importanza e risolve una serie di problemi di progettazione esistenti in vari modi, quindi non dovrebbe essere trascurata. Tieni a mente queste due alternative quando progetti il ​​tuo modello orientato agli oggetti.

12. INCAPSULAMENTO.

Il concetto di incapsulamento nella programmazione orientata agli oggetti consiste nel nascondere tutti i dettagli di implementazione (come modalità operativa, metodi interni, ecc.) al mondo esterno. I vantaggi dell'incapsulamento sono la manutenibilità e la facilità di modifica. L'implementazione interna della classe è nascosta, il lavoro con i dati della classe avviene esclusivamente attraverso i metodi pubblici della classe (un vero problema se si sta sviluppando una libreria o un framework utilizzato da molte persone). L'incapsulamento in Java si ottiene attraverso regole di visibilità e accessibilità. In Java, è considerata buona pratica non esporre mai i campi direttamente, ma solo tramite getter e setter (a meno che i campi non siano contrassegnati come finali). Per esempio:
package com.javacodegeeks.advanced.design;

public class Encapsulation {
    private final String email;
    private String address;

    public Encapsulation( final String email ) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }
}
Questo esempio ricorda quelli che nel linguaggio Java vengono chiamati JavaBeans : le classi Java standard sono scritte secondo un insieme di convenzioni, una delle quali consente l'accesso ai campi solo tramite metodi getter e setter. Come abbiamo già sottolineato nella sezione sull'ereditarietà, si prega di rispettare sempre il contratto minimo di pubblicità in una classe, utilizzando i principi dell'incapsulamento. Tutto ciò che non dovrebbe essere pubblico dovrebbe diventare privato (o protetto/pacchetto privato, a seconda del problema che stai risolvendo). Ciò ti ripagherà a lungo termine dandoti la libertà di progettare senza (o almeno minimizzando) modifiche importanti. 

13. LEZIONI FINALI E METODI

In Java esiste un modo per evitare che una classe diventi una sottoclasse di un'altra classe: l'altra classe deve essere dichiarata finale. 
package com.javacodegeeks.advanced.design;

public final class FinalClass {
}
La stessa parola chiave finale in una dichiarazione di metodo impedisce alle sottoclassi di sovrascrivere il metodo. 
package com.javacodegeeks.advanced.design;

public class FinalMethod {
    public final void performAction() {
    }
}
Non esistono regole generali per decidere se una classe o un metodo debbano essere definitivi o meno. Le classi e i metodi finali limitano l'estensibilità ed è molto difficile pensare in anticipo se una classe debba o meno essere ereditata, o se un metodo debba o meno essere sovrascritto in futuro. Ciò è particolarmente importante per gli sviluppatori di librerie, poiché decisioni di progettazione come questa potrebbero limitare in modo significativo l'applicabilità della libreria. La Java Standard Library ha diversi esempi di classi finali, la più famosa è la classe String. Questa decisione è stata presa in una fase iniziale per impedire qualsiasi tentativo da parte degli sviluppatori di trovare una propria soluzione “migliore” per l’implementazione delle stringhe. 

14. COSA C'È DOPO

In questa parte della lezione abbiamo trattato i concetti della programmazione orientata agli oggetti in Java. Abbiamo dato un rapido sguardo anche alla programmazione contrattuale, toccando alcuni concetti funzionali e vedendo come il linguaggio si è evoluto nel tempo. Nella parte successiva della lezione incontreremo i generici e il modo in cui cambiano il modo in cui affrontiamo la sicurezza dei tipi nella programmazione. 

15. SCARICA IL CODICE SORGENTE

Puoi scaricare il sorgente qui - advanced-java-part-3 Sorgente: Come progettare classi an
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION