Il modello di progettazione per garantire la "firma" delle liste generiche è uguale

2

Ho un'interfaccia, Attribute , che sarà implementata in vari modi,

class AttributeA implements Attribute{\..}
class AttributeB implements Attribute{\..} 

Quindi ho intenzione di avere una classe contenente un elenco di membri dell'interfaccia

class Example { 
    List<Attribute> attributes;
    //..
} 

E poi ho intenzione di avere una classe contenente un elenco di quelli

class ExamplesAndMore { 
    List<Example> examples;
    //..
} 

Ma, voglio ogni instatiation di ExamplesAndMore per assicurarmi che ci siano solo esempi dello stesso tipo o firma. Cioè, voglio assicurarmi che la lista examples non contenga un membro la cui lista attributes abbia la forma <AttributeA, AttributeA, AttributeB> mentre un'altra è di <AttributeA, AttributeA, AttributeA> o <AttributeA, AttributeA, AttributeB, AttributeB> .

Potrei controllare ovunque io modifichi la lista examples .

Un commento mi ha chiesto di essere più specifico, quindi fornirò il codice per sapere come farlo.

class Example { 
    List<Attribute> attributes;

    public List<Class<? extends Attribute>> getSignature(){
        List<Class<? extends Attribute>> result = new ArrayList<Class<? extends Attribute>>();
        for(Attribute attribute : attributes){
            result.add(attribute.getClass());
        }

        return result;
    }

    //..
}

public class ExamplesAndMore {
    List<Example> examples;
    List<Class<? extends Attribute>> signature;

    public ExamplesAndMore(List<Class<? extends Attribute>> signature){
        this.signature = signature;
        this.examples = new ArrayList<Example>();
    }

    public void addExample(Example example){
        if(!example.getSignature().equals(signature)){
            throw new IllegalArgumentException(String.format("This takes examples with signature %s", signature.toString()));
        }
        examples.add(example);
    }

    //..
}

Ma mi chiedo se questo è un buon modo per farlo, controllare i tipi in fase di esecuzione non è piacevole. Fondamentalmente penso che questo codice abbia un odore un po '. Un modo in cui questo odore potrebbe manifestarsi come un problema reale è quando creo

class GenericAttribute<T> implements Attribute{\..}  

con l'intenzione di non avere un esempio con un attributo di tipo GenericAttribute<ClassA> e uno di tipo GenericAttribute<ClassB> nella stessa lista. Quindi digita le rovine della mia giornata.

Inoltre, quando non ho bisogno di esempi per contenere arbitrariamente molti attributi ma solo un numero fisso (diciamo 3), allora il mio problema scompare completamente e posso anche verificare in fase di compilazione avendo

class Example<T1, T2, T3> { 
    //..
}

public class ExamplesAndMore<T1,T2,T3> {
    List<Example<T1,T2,T3> examples;

    public void addExample(Example<T1,T2,T3> example){\..}
    //..
}

Tuttavia, questo approccio ovviamente non si estende agli esempi con arbitrariamente molti attributi.

Ad ogni modo, sento che mi manca qualche tecnica ovvia o qualcosa del genere. C'è un bel modello che si occupa di questo, sto progettando male questo? È probabile che io non sia il primo a farlo.

Mi dispiace anche per il titolo schifoso, Se avessi una descrizione migliore del mio problema, probabilmente avrei avuto più fortuna a cercare una risposta e non avrei dovuto chiedere ...

    
posta Hirle 26.05.2015 - 13:57
fonte

2 risposte

2

Sembra un po 'strano che Example.attributes possa contenere una miscela di AttributeA e AttributeB oggetti, ma ExamplesAndMore.examples deve contenere un oggetto "omogeneo" (che usa quel poco) List di Example . In che modo dovrebbero essere usati questi, per entrambe le classi Example e ExamplesAndMore ? Esiste anche una sorta di ordinamento / equivalenza implicita tra le voci di ExamplesAndMore.examples ? Potrebbe che essere un altro odore di codice?

Tuttavia, restando sull'argomento, potresti volere un metodo all'interno di Example che possa descrivere ragionevolmente il contenuto di se stesso ... questo può essere semplice come concatenare i nomi delle classi delle proprie voci, come illustrato di seguito. Penso che sia abbastanza sicuro poiché List ha un ordine di iterazione prevedibile.

public String getEntriesDescription() {
    return attributes.stream().map(v -> v.getClass().getName())
                        .collect(Collectors.joining(";"));
}

Quando aggiungi un oggetto Example a ExamplesAndMore , puoi utilizzarlo per eseguire il confronto che vuoi ...

public void addExample(Example example) {
    // assuming you have a field exampleDescription
    if (!exampleDescription.equals(example.getEntriesDescription()) {
        throw new IllegalArgumentException("Example description does not match.");
    }
    examples.add(example);
}

Questo è abbastanza simile a quello che hai in mente.

    
risposta data 28.05.2015 - 02:54
fonte
0

Se class AttributeA implementa Attribute e class AttributeB implementa lo stesso, ciò significa: in un certo senso AttributeA si comporta come AttributeB ; e il livello di astrazione su cui lo fanno, si comporta come Attribute . Ecco come funziona il polimorfismo!

But, I want any instatiation of ExamplesAndMore to make sure that it only takes examples of the same type or signature.

Il modo corretto di implementarlo - secondo il tuo progetto iniziale sarebbe avere una classe separata di Example che ha una lista di attributi di un tipo, cioè hai bisogno di un altro Interface per Example che permetta di distinguere tra Example containing AttributeA e Example containing AttributeB . Allora sei a) sicuro che il concreto Example contiene solo gli attributi che vuoi e b) potrebbe usare l'astrazione dell'interfaccia per trattarli allo stesso modo in questo senso.

Alla fine, potresti definire la tua classe ExamplesAndMore , come intesa: contenente Examples , ma non le classi, ma piuttosto le interfacce.

    
risposta data 25.10.2015 - 10:30
fonte

Leggi altre domande sui tag