Qual è il nome del seguente modello (anti)? Quali sono i suoi vantaggi e svantaggi?


28

Negli ultimi mesi, sono inciampato alcune volte sulla seguente tecnica / modello. Tuttavia, non riesco a trovare un nome specifico, né sono sicuro al 100% di tutti i suoi vantaggi e svantaggi.

Il modello è il seguente:

All'interno di un'interfaccia Java, una serie di metodi comuni è definita come al solito. Tuttavia, utilizzando una classe interna, un'istanza predefinita viene trapelata attraverso l'interfaccia.

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

Per me, sembra che il vantaggio maggiore risieda nel fatto che uno sviluppatore deve solo conoscere l'interfaccia e non le sue implementazioni, ad esempio nel caso in cui voglia rapidamente creare un'istanza.

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

Inoltre, ho visto questa tecnica utilizzata insieme a Spring per fornire dinamicamente istanze a seconda della configurazione. A questo proposito, sembra anche che questo possa aiutare con la modularizzazione.

Tuttavia, non riesco a scuotere la sensazione che si tratti di un uso improprio dell'interfaccia poiché associa l'interfaccia a una delle sue implementazioni. (Principio di inversione di dipendenza ecc.) Qualcuno potrebbe spiegarmi come si chiama questa tecnica, nonché i suoi vantaggi e svantaggi?

Aggiornare:

Dopo qualche tempo di riflessione, ho ricontrollato e ho notato che la seguente versione singleton del modello è stata utilizzata molto più spesso. In questa versione, un'istanza statica pubblica viene esposta attraverso l'interfaccia che viene inizializzata una sola volta (a causa del campo finale). Inoltre, l'istanza viene quasi sempre recuperata utilizzando Spring o una factory generica che disaccoppia l'interfaccia dall'implementazione.

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

In breve: sembra che si tratti di un modello singleton / factory personalizzato, che sostanzialmente consente di esporre un'istanza o un singleton attraverso la sua interfaccia. Per quanto riguarda gli svantaggi, alcuni sono stati indicati nelle risposte e nei commenti seguenti. Finora, il vantaggio sembra risiedere nella sua convenienza.


una specie di modello singleton?
Bryan Chen,

Penso che tu abbia ragione che l'interfaccia non dovrebbe "conoscere" le sue implementazioni. Probabilmente Vehicle.Defaultdovrebbe essere spostato nello spazio dei nomi del pacchetto come una classe di fabbrica, ad es VehicleFactory.
agosto

7
A proposito,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski il

20
L'antipasto qui è usare l'antipasto di analogia per auto, strettamente correlato all'antipasto di analogia per animali. La maggior parte dei software non ha ruote o gambe.
Pieter B,

@PieterB: :-)))) Mi hai reso felice!
Doc Brown,

Risposte:


24

Default.getInstanceIMHO è solo una forma molto specifica di un metodo factory , mescolato con una convenzione di denominazione presa dal modello singleton (ma senza essere un'implementazione di quest'ultimo). Nella forma attuale questa è una violazione del " principio della singola responsabilità ", poiché l'interfaccia (che ha già lo scopo di dichiarare un'API di classe) si assume la responsabilità aggiuntiva di fornire un'istanza predefinita, che sarebbe molto meglio collocata in un VehicleFactoryclasse.

Il problema principale causato da questo costrutto è che induce una dipendenza ciclica tra Vehiclee Car. Ad esempio, nella forma corrente non sarà possibile inserirli Vehiclein una libreria e Carin un'altra. Utilizzando una classe di fabbrica separata e inserendo il Default.getInstancemetodo lì si risolverebbe questo problema. Potrebbe anche essere una buona idea dare a quel metodo un nome diverso, per evitare qualsiasi confusione legata ai singoli. Quindi il risultato potrebbe essere qualcosa di simile

VehicleFactory.createDefaultInstance()


Grazie per la tua risposta! Sono d'accordo che questo esempio particolare è una violazione dell'SRP. Tuttavia, non sono sicuro che si tratti ancora di una violazione nel caso in cui l'implementazione venga recuperata in modo dinamico. Ad esempio utilizzando Spring (o un framework simile) per recuperare una particolare istanza definita ad esempio da un file di configurazione.
Jérôme,

1
@Jérôme: IMHO una classe non nidificata denominata VehicleFactoryè più convenzionale del costrutto nidificato Vehicle.Default, in particolare con il metodo "getInstance" fuorviante. Sebbene sia possibile aggirare la dipendenza ciclica usando Spring, preferirei personalmente la prima variante solo per il buon senso. E ti lascia comunque la possibilità di spostare la fabbrica in un altro componente, cambiando successivamente l'implementazione per lavorare senza Spring, ecc.
Doc Brown,

Spesso, la ragione per usare un'interfaccia piuttosto che una classe è quella di consentire alle classi che hanno altri scopi di essere utilizzabili dal codice che richiede un'implementazione dell'interfaccia. Se una classe di implementazione predefinita è in grado di soddisfare tutte le esigenze del codice che necessita semplicemente di qualcosa che implementa l'interfaccia in modo "normale", specificare un modo per creare un'istanza sembrerebbe utile per l'interfaccia.
supercat

Se Car è un'implementazione standard molto generica di Veicolo, questa non è una violazione di SRP. Il veicolo ha ancora un solo motivo per cambiare, ad esempio quando Google aggiunge un autodrive()metodo. La tua auto molto generica deve quindi cambiare per implementare quel metodo, ad esempio lanciando un NotImplementedException, ma ciò non conferisce un motivo aggiuntivo per cambiare sul veicolo.
user949300,

@ user949300: l'SRP non è un concetto "dimostrabile matematicamente". E le decisioni sulla progettazione del software non sono sempre "in bianco e nero". Il codice originale non è poi così male che non poteva essere usato nel codice di produzione, ovviamente, e ci possono essere casi in cui la convenienza supera la dipendenza ciclica, come ha notato l'OP nel suo "aggiornamento". Quindi, per favore leggi la mia risposta come "considera se l'uso di una classe di fabbrica separata non ti serve meglio". Nota anche l'autore ha cambiato la sua domanda originale un po 'dopo che ho dato la mia risposta.
Doc Brown,

3

A me sembra un modello Null Object .

Ma ciò dipende fortemente da come Carviene implementata la classe. Se è implementato in modo "neutro", allora è sicuramente un oggetto nullo. Ciò significa che se è possibile rimuovere tutti i controlli null sull'interfaccia e inserire l'istanza di questa classe anziché ogni occorrenza di null, il codice deve comunque funzionare correttamente.

Anche se si tratta di questo modello, non vi è alcun problema di creare un accoppiamento stretto tra l'interfaccia stessa e l'implementazione dell'oggetto null. Perché l'oggetto null può trovarsi ovunque dove viene utilizzata detta interfaccia.


2
Ciao e grazie per la risposta. Anche se sono abbastanza sicuro che nel mio caso questo non sia stato utilizzato per implementare il modello "Null Object", è una spiegazione interessante.
Jérôme,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.