È questa la soluzione all'eredità statica?

2

Recentemente ho visto molti post sul perché i Singletons dovrebbero essere evitati. Tuttavia, non riesco a vedere nessuno di questi problemi con la seguente soluzione a un problema comune: l'ereditarietà statica.

Ad esempio, di recente stavo lavorando su una libreria che cancella il codice sorgente HTML di un sito Web e ne analizza il contenuto in oggetti. Il problema qui è che il sito ha diversi tipi di contenuti - articoli, video, ecc. Nel mio codice iniziale, ho avuto diverse diverse classi di "fornitori" statici, ovvero ArticleSupplier , che ho trovato rapidamente condiviso un sacco di codice boilerplate (ad es. verifica degli argomenti).

Avrei potuto rendere le classi basate su istanze, ma sembra inutile: new ArticleSupplier() non ha stato; tutti i suoi metodi dipendono dagli argomenti passati a loro. Istanziare un altro è solo uno spreco di memoria.

Questo mi ha portato a pensare all'utilizzo del pattern Singleton: una classe base può eseguire controlli di codice di tipo standard e quindi chiamare l'implementazione della classe derivata; la classe derivata potrebbe quindi controllare l'accesso con un singleton. Ecco un semplice esempio in C #:

abstract class Supplier
{
    IEnumerable<IModel> RecentModels(int amount)
    {
        return ModelsBefore(DateTime.Now, amount);
    }
    IEnumerable<IModel> ModelsBefore(DateTime date, int amount)
    {
        CheckDate(date);
        CheckAmount(amount);
        return InternalBefore(date, amount);
    }
    abstract IEnumerable<IModel> InternalBefore(DateTime date, int amount);
}

class ArticleSupplier
{
    private static readonly ArticleSupplier _Instance = new ArticleSupplier();
    private ArticleSupplier() { }
    public static ArticleSupplier Instance { get { return _Instance; } }
    override IEnumerable<IModel> InternalBefore(DateTime date, int amount)
    {
        // implementation
    }
}

Ora, puoi concentrarti sull'implementazione entro ArticleSupplier senza scrivere lo stesso codice boilerplate e allo stesso tempo controllare l'istanza dell'oggetto. Vorrei sottolineare ancora una volta che questo è completamente sicuro per i thread perché il singleton non ha stato , motivo per cui il metodo era statico in primo luogo.

È un uso appropriato del pattern Singleton, o ci sono ancora alcune trappole che mi mancano?

    
posta James Ko 28.05.2015 - 03:07
fonte

2 risposte

5

Is this an appropriate use of the Singleton pattern, or are there still some pitfalls that I'm missing?

No, questo ha quasi tutte le insidie dei singleton e anche questo presuppone che tu effettivamente usi Supplier quasi ovunque e abbia altre implementazioni. Non ho intenzione di addentrarmi nelle varie insidie dei singleton, perché sono stati trattati bene altrove.

Instantiating another one is just a waste of memory.

4 byte. Stai limitando la progettazione per 4 byte ? Sì, al momento non ha stato. Sì, è attualmente protetto da thread. Sembra del tutto plausibile che una versione futura abbia bisogno di parametrizzare la posizione dei tuoi articoli, o forse alcune informazioni su come gli articoli sono divisi per il tempo di creazione, o da un numero qualsiasi di cose. Ma tu vuoi ostacolare te stesso a causa di 4 byte?

Il singleton è un antipattern. Non lo cambierai.

    
risposta data 28.05.2015 - 04:21
fonte
2

Quello che stai cercando non è un singleton, ma una fabbrica. Il motivo è che hai un singolo oggetto che individua la logica da eseguire, quindi la esegue. Come dici nel tuo commento, è funzionalmente simile a un'istruzione switch : scopri quale ramo eseguire, quindi eseguilo.

Funziona bene se la tua logica è semplice, ma una volta che si sviluppa in complessità, il switch o la cascata if diventano ingombranti. Inoltre, si desidera che il codice carichi le implementazioni in modo dinamico, non tramite la selezione in fase di compilazione. È meglio seppellire questa logica in un oggetto invece di un metodo lungo. Raccomando i seguenti componenti:

  • Ogni "metodo" o algoritmo, cioè ogni "fornitore" nel tuo esempio, ha una coppia di classi:

    • Un vero fornitore che fa tutto ciò di cui ha bisogno il tuo problema.

    • Un fornitore di servizi che determina se questo particolare fornitore può gestire l'input ed è in grado di creare il fornitore.

  • Una classe che rappresenta i dati di input per passare facilmente tra i metodi (non è necessario il sovraccarico).

  • Una classe factory che accetta input e restituisce un oggetto in grado di elaborarlo.

Esempio in pseudocodice:

class Input {
  public DateRange range;
  public int quantity;
}

interface ServiceProvider {
  boolean canHandle(Input i);
  object newHandler(object o);
}

class Factory {
  List<ServiceProvider> sps = new ArrayList<>();

  public static void register(ServiceProvider sp) {
    sps.add(sp);
  }

  public static Supplier getSupplier(Input i) {
    for (ServiceProvider sp in sps) {
      if (sp.canHandle(i)) {
        return sp.newHandler(i);
      }
    }
    return null;
  }
}

Note:

  • Sostituisce switch con l'invio dinamico tramite l'attraversamento della lista e la ricerca di un'istanza in grado di gestire l'input.

  • Questo è un semplice esempio per darti l'idea. A seconda del problema specifico, potrebbe essere preferibile utilizzare una tabella hash o un'altra struttura dati.

  • I fornitori di servizi devono registrarsi. Il meccanismo specifico per farlo dipende dalla lingua (la tua domanda non è etichettata con una lingua specifica).

    • Java può utilizzare l' Interfaccia provider di servizi .

    • C ++, collegamento statico: basta chiamare una funzione di inizializzazione per impostarlo prima dell'uso.

    • C ++, collegamento dinamico: configuralo in DllMain o nel punto di ingresso equivalente per la tua piattaforma.

    • C #: puoi implementare l'interfaccia IServiceProvider .

risposta data 28.05.2015 - 03:38
fonte