Quando utilizzare i generici nella progettazione dell'interfaccia

11

Ho alcune interfacce che intendo implementare in futuro da terze parti e fornisco personalmente un'implementazione di base. Userò solo un paio per mostrare l'esempio.

Attualmente sono definiti come

Articolo:

public interface Item {

    String getId();

    String getName();
}

ItemStack:

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer:

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

Ora, Item e ItemStackFactory posso assolutamente prevedere che alcune terze parti debbano estenderlo in futuro. ItemStackContainer potrebbe anche essere esteso in futuro, ma non in modi che io possa prevedere, al di fuori della mia implementazione predefinita fornita.

Ora, sto cercando di rendere questa libreria il più solida possibile; questo è ancora nelle fasi iniziali (pre-pre-alpha), quindi questo può essere un atto di over engineering (YAGNI). È un posto appropriato per usare i generici?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

E

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

Temo che questo possa finire per rendere le implementazioni e l'utilizzo più difficili da leggere e capire; Penso che sia la raccomandazione di evitare l'annidamento dei farmaci generici laddove possibile.

    
posta Zymus 01.09.2015 - 04:51
fonte

6 risposte

8

Il tuo piano su come introdurre la generalità nella tua interfaccia sembra essere corretto per me. Tuttavia, la tua domanda se questa sarebbe una buona idea o meno richiede una risposta un po 'più complicata.

Nel corso degli anni ho intervistato un certo numero di candidati per incarichi di ingegneria del software per conto delle aziende per cui ho lavorato, e ho capito che la maggior parte delle persone in cerca di lavoro là fuori si sentono a proprio agio con utilizzando classi generiche, (ad esempio, le classi di raccolta standard), ma non con le classi generiche scrittura . Molti non hanno mai scritto una singola classe generica nella loro carriera, e sembrano come se fossero completamente persi se gli fosse chiesto di scriverne uno. Quindi, se imponi l'uso di farmaci generici a chiunque desideri implementare la tua interfaccia o estendere le tue implementazioni di base, potresti mettere fuori gioco le persone.

Ciò che puoi provare, tuttavia, è fornire versioni generiche e non generiche delle tue interfacce e delle tue implementazioni di base, in modo che le persone che non fanno i generici possano vivere felici nel loro mondo ristretto e pesante come il cast, mentre le persone che fanno i generici possono trarre beneficio dalla loro più ampia comprensione della lingua.

    
risposta data 01.09.2015 - 13:55
fonte
7

Usi i generici nell'interfaccia quando è probabile che anche l'implementazione sia generica.

Ad esempio, qualsiasi struttura dati che può accettare oggetti arbitrari è un buon candidato per un'interfaccia generica. Esempi: List<T> e Dictionary<K,V> .

Ogni situazione in cui si desidera migliorare la sicurezza del tipo in modo generalizzato è un buon candidato per i generici. Un List<String> è un elenco che funziona solo su stringhe.

Qualsiasi situazione in cui si desidera applicare SRP in modo generalizzato ed evitare metodi specifici del tipo è un buon candidato per i generici. Ad esempio, PersonDocument, PlaceDocument e ThingDocument possono essere sostituiti con Document<T> .

Il pattern Abstract Factory è un buon esempio per un generico, mentre un normale metodo Factory creerebbe semplicemente oggetti che ereditano da un'interfaccia comune e concreta.

    
risposta data 01.09.2015 - 09:12
fonte
3

Now, Item and ItemStackFactory I can absolutely foresee some third-party needing to extend it in the future.

C'è una tua decisione presa per te. Se non fornisci generici, dovranno eseguire il cast up-cast per l'implementazione di Item ogni volta che usano:

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

Dovresti almeno rendere ItemStack generico nel modo che suggerisci. Il beneficio più profondo dei generici è che non hai quasi mai bisogno di trasmettere nulla, e offrire codice che costringe gli utenti a lanciare è IMHO un reato penale.

    
risposta data 01.09.2015 - 17:31
fonte
0

Penso che usare i generici nella tua situazione sia troppo complesso.

Se scegli una versione generica di interfacce, il design indica che

  1. ItemStackContainer < A > contiene un singolo tipo di stack, A. (vale a dire che ItemStackContainer non può contenere più tipi di stack.)
  2. ItemStack è dedicato a un tipo specifico di oggetto. Quindi non esiste un tipo di astrazione di tutti i tipi di stack.
  3. È necessario definire una specifica ItemStackFactory per ciascun tipo di articoli. È considerato troppo fine come Modello di fabbrica.
  4. Scrivi più codice difficile come ItemStack < ? estende l'elemento & gt ;, riduzione della manutenibilità.

Queste assunzioni e restrizioni implicite sono cose molto importanti da decidere con attenzione. Quindi, specialmente all'inizio, questi progetti prematuri non dovrebbero essere introdotti. È semplice, facile da capire, un design comune.

    
risposta data 02.09.2015 - 10:43
fonte
0

Direi che dipende principalmente da questa domanda: se qualcuno ha bisogno di estendere la funzionalità di Item e ItemStackFactory ,

  • sovrascriveranno solo i metodi e quindi le istanze delle loro sottoclassi verranno utilizzate proprio come le classi base, solo si comportano in modo diverso?
  • o aggiungeranno membri e useranno le sottoclassi in modo diverso?

Nel primo caso, non c'è bisogno di generici, nel secondo caso probabilmente c'è.

    
risposta data 02.09.2015 - 11:04
fonte
0

Forse, puoi avere una gerarchia di interfacce, in cui Foo è un'interfaccia, Bar è un'altra. Ogni volta che un tipo deve essere entrambi, è un FooAndBar.

Per evitare di sovrascrivere, basta creare il tipo FooAndBar quando è necessario e mantenere un elenco di interfacce che non estenderanno mai nulla.

Questo è molto più comodo per la maggior parte dei programmatori (più come il loro tipo di controllo o come non avere mai a che fare con la tipizzazione statica di qualsiasi tipo). Pochissime persone hanno scritto la loro lezione di generici.

Anche come nota: le interfacce riguardano le possibili azioni che possono essere eseguite. Dovrebbero effettivamente essere verbi e non nomi.

    
risposta data 11.05.2016 - 22:16
fonte

Leggi altre domande sui tag