JavaRush /Java Blog /Random-IT /Modelli di progettazione: Singleton

Modelli di progettazione: Singleton

Pubblicato nel gruppo Random-IT
Ciao! Oggi daremo uno sguardo più da vicino ai diversi modelli di design e inizieremo con il modello Singleton, chiamato anche “singleton”. Modelli di progettazione: Singleton - 1Ricordiamo: cosa sappiamo dei design pattern in generale? I modelli di progettazione sono best practice che possono essere seguite per risolvere una serie di problemi noti. I modelli di progettazione generalmente non sono legati ad alcun linguaggio di programmazione. Prendili come una serie di raccomandazioni, in seguito alle quali puoi evitare errori e non reinventare la tua ruota.

Cos'è un singleton?

Un singleton è uno dei modelli di progettazione più semplici che possono essere applicati a una classe. Le persone a volte dicono "questa classe è un singleton", il che significa che questa classe implementa il modello di progettazione singleton. A volte è necessario scrivere una classe per la quale è possibile creare un solo oggetto. Ad esempio, una classe responsabile della registrazione o della connessione a un database. Il modello di progettazione Singleton descrive come possiamo eseguire tale attività. Un singleton è un modello di progettazione che fa due cose:
  1. Fornisce la garanzia che una classe avrà una sola istanza della classe.

  2. Fornisce un punto di accesso globale a un'istanza di questa classe.

Quindi, ci sono due caratteristiche che sono caratteristiche di quasi ogni implementazione del modello singleton:
  1. Costruttore privato. Limita la possibilità di creare oggetti di classe al di fuori della classe stessa.

  2. Un metodo statico pubblico che restituisce un'istanza della classe. Questo metodo è chiamato getInstance. Questo è il punto di accesso globale all'istanza della classe.

Opzioni di implementazione

Il modello di progettazione singleton viene utilizzato in diversi modi. Ogni opzione è buona e cattiva a modo suo. Qui, come sempre: non esiste un ideale, ma bisogna lottare per raggiungerlo. Ma prima di tutto, definiamo cosa è buono e cosa è cattivo, e quali metriche influenzano la valutazione dell'implementazione di un design pattern. Cominciamo con il positivo. Ecco i criteri che conferiscono succosità e attrattiva all'implementazione:
  • Inizializzazione lazy: quando una classe viene caricata mentre l'applicazione è in esecuzione esattamente quando è necessaria.

  • Semplicità e trasparenza del codice: la metrica, ovviamente, è soggettiva, ma importante.

  • Sicurezza del thread: funziona correttamente in un ambiente multi-thread.

  • Prestazioni elevate in un ambiente multi-thread: i thread si bloccano a vicenda minimamente o per niente quando condividono una risorsa.

Ora i contro. Elenchiamo i criteri che mettono in cattiva luce l'implementazione:
  • Inizializzazione non pigra: quando una classe viene caricata all'avvio dell'applicazione, indipendentemente dal fatto che sia necessaria o meno (un paradosso, nel mondo IT è meglio essere pigri)

  • Complessità e scarsa leggibilità del codice. Anche la metrica è soggettiva. Daremo per scontato che se il sangue esce dagli occhi, l'implementazione è così così.

  • Mancanza di sicurezza del thread. In altre parole, “pericolo di thread”. Operazione non corretta in un ambiente multi-thread.

  • Scarse prestazioni in un ambiente multi-thread: i thread si bloccano a vicenda continuamente o spesso quando condividono una risorsa.

Codice

Ora siamo pronti a considerare le varie opzioni di implementazione, elencando i pro e i contro:

Soluzione semplice

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
L'implementazione più semplice. Professionisti:
  • Semplicità e trasparenza del codice

  • Sicurezza del filo

  • Prestazioni elevate in un ambiente multi-thread

Aspetti negativi:
  • Inizializzazione non pigra.
Nel tentativo di correggere l'ultimo difetto, otteniamo l'implementazione numero due:

Inizializzazione pigra

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Professionisti:
  • Inizializzazione pigra.

Aspetti negativi:
  • Non thread-safe

Interessante l'implementazione. Possiamo inizializzare pigramente, ma abbiamo perso la sicurezza del thread. Nessun problema: nell'implementazione numero tre sincronizziamo tutto.

Accesso sincronizzato

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Professionisti:
  • Inizializzazione pigra.

  • Sicurezza del filo

Aspetti negativi:
  • Scarse prestazioni in un ambiente multi-thread

Grande! Nell'implementazione numero tre, abbiamo ripristinato la sicurezza del thread! È vero, è lento... Ora il metodo getInstanceè sincronizzato e puoi inserirlo solo uno alla volta. Infatti non abbiamo bisogno di sincronizzare l'intero metodo, ma solo quella parte di esso in cui inizializziamo un nuovo oggetto classe. Ma non possiamo semplicemente racchiudere synchronizedla parte responsabile della creazione di un nuovo oggetto in un blocco: questo non garantirà la sicurezza del thread. E' un po' più complicato. Il metodo di sincronizzazione corretto è riportato di seguito:

Chiusura a doppio controllo

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Professionisti:
  • Inizializzazione pigra.

  • Sicurezza del filo

  • Prestazioni elevate in un ambiente multi-thread

Aspetti negativi:
  • Non supportato nelle versioni Java precedenti alla 1.5 (la parola chiave volatile è stata corretta nella versione 1.5)

Prendo atto che affinché questa opzione di implementazione funzioni correttamente, è richiesta una delle due condizioni. La variabile INSTANCEdeve essere final, o volatile. L'ultima implementazione di cui parleremo oggi è Class Holder Singleton.

Titolare della Classe Singleton

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Professionisti:
  • Inizializzazione pigra.

  • Sicurezza del filo.

  • Prestazioni elevate in un ambiente multi-thread.

Aspetti negativi:
  • Per un corretto funzionamento è necessario garantire che l'oggetto classe Singletonvenga inizializzato senza errori. In caso contrario, la prima chiamata al metodo getInstanceterminerà con un errore ExceptionInInitializerErrore tutte le successive falliranno NoClassDefFoundError.

L'implementazione è quasi perfetta. E pigro, thread-safe e veloce. Ma c'è una sfumatura descritta in meno. Tabella comparativa delle varie implementazioni del pattern Singleton:
Implementazione Inizializzazione pigra Sicurezza del filo Velocità multithread Quando usare?
Soluzione semplice - + Veloce Mai. O quando l'inizializzazione pigra non è importante. Ma mai meglio.
Inizializzazione pigra + - Non applicabile Sempre quando il multithreading non è necessario
Accesso sincronizzato + + Lentamente Mai. O quando la velocità di lavoro con il multithreading non ha importanza. Ma mai meglio
Chiusura a doppio controllo + + Veloce In rari casi in cui è necessario gestire le eccezioni durante la creazione di un singleton. (quando il titolare della classe Singleton non è applicabile)
Titolare della Classe Singleton + + Veloce Sempre quando è necessario il multithreading e c'è la garanzia che un oggetto di classe singleton verrà creato senza problemi.

Pro e contro del modello Singleton

In generale, il singleton fa esattamente ciò che ci si aspetta da esso:
  1. Fornisce la garanzia che una classe avrà una sola istanza della classe.

  2. Fornisce un punto di accesso globale a un'istanza di questa classe.

Tuttavia, questo modello presenta degli svantaggi:
  1. Singleton viola il SRP (Principio di responsabilità unica): la classe Singleton, oltre alle sue responsabilità immediate, controlla anche il numero delle sue copie.

  2. La dipendenza di una classe o di un metodo regolare da un singleton non è visibile nel contratto pubblico della classe.

  3. Le variabili globali sono dannose. Il singleton alla fine si trasforma in una variabile globale pesante.

  4. La presenza di un singleton riduce la testabilità dell'applicazione in generale e delle classi che utilizzano il singleton in particolare.

OK, è tutto finito adesso. Abbiamo esaminato il modello di progettazione singleton. Ora, in una conversazione per la vita con i tuoi amici programmatori, sarai in grado di dire non solo ciò che è positivo, ma anche qualche parola su ciò che è negativo. Buona fortuna per padroneggiare nuove conoscenze.

Lettura aggiuntiva:

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION