2009-11-02 4 views
5

Scusate per il titolo scarso, non riesco a pensare a un modo succinto di mettere questo ..È possibile evitare l'uso della verifica del tipo in questo esempio?

Sto pensando di avere una lista di oggetti che saranno tutti di una specifica interfaccia. Ognuno di questi oggetti può quindi implementare ulteriori interfacce, ma non è garantito quale oggetto implementerà quale. Tuttavia, in un singolo ciclo, desidero essere in grado di chiamare i metodi di qualunque sia il loro ulteriore sottotipo.

Ie, 3 interfacce:

public interface IAnimal { ... } 
public interface IEggLayer { public Egg layEgg(); } 
public interface IMammal { public void sweat(); } 

questo sarebbe poi memorizzata come

private List<IAnimal> animals= new ArrayList<IAnimal>(); 

modo, istanze aggiunti all'elenco potrebbe anche essere di tipo IEggLayer o IMammal, che hanno completamente estranei metodi.

Il mio istinto sarebbe quello di poi fare

for(IAnimal animal : animals) { 
    if(animal instanceof IEggLayer) { 
    egg = ((IEggLayer)animal).layEgg(); 
    } 
    if(animal instance of IMammal) { 
    ((IMammal)animal).sweat(); 
    } 
} 

ma ho sempre stato detto che il controllo di tipo è un segno che il codice in realtà dovrebbe essere riscritta.

Dal momento che potrebbe essere possibile per un singolo oggetto eseguire entrambi [ornitorinco, ad esempio], nel senso che un singolo doFunction() non sarebbe adatto qui, è possibile evitare l'uso del controllo di tipo in questo caso, oppure è un esempio in cui il controllo del tipo è classificato come accettabile?
Esiste forse un motivo di progettazione adatto a questo?

Mi scuso per l'esempio inventato così ...
[Ignorare eventuali errori di sintassi, si prega - è destinato solo per essere simile a Java pseudocodice]

Ho aggiunto lvalue all'uso EggLayer, a mostra che a volte il tipo di reso è importante

+0

questo codice sembra essere java, perché la bandiera C#? –

+0

perché qualcun altro l'ha aggiunto. Posso rimuovere il tag java che è stato aggiunto anche io, poiché spero di mantenere questa lingua indipendente – Farrell

risposta

1

Ma mi è sempre stato detto che il controllo di tipo è un segno che il codice dovrebbe essere effettivamente refactored.

è un segno che o gerarchia di classe o il codice che lo utilizza può bisogno di essere riscritta o ristrutturati. Ma spesso non ci sarà nessun refactoring/ristrutturazione che eviti il ​​problema.

In questo caso, dove si hanno metodi che si applicano solo a sottotipi specifici, il refactoring più promettente sarebbe quello di avere liste separate per gli animali che sono strati di uova e gli animali che sudano.

Ma se non è possibile farlo, è necessario eseguire un controllo di tipo. Anche lo isEggLayer()/isMammal() prevede un controllo del tipo; per esempio.

if (x.isEggLayer()) { 
    ((IEggLayer) x).layEgg(); // type cast is required. 
} 

Suppongo che si potrebbe nascondere il controllo di tipo tramite un metodo asEggLayer(); per esempio.

public IEggLayer asEggLayer() { 
    return ((IEggLayer) this); 
} 

o

// Not recommended ... 
public IEggLayer asEggLayer() { 
    return (this instanceof IEggLayer) ? ((IEggLayer) this) : null; 
} 

Ma c'è sempre un TYPECHECK accadendo, e la possibilità che fallirà. Inoltre, tutti questi tentativi di nascondere il controllo dei tipi comportano l'aggiunta di "conoscenza" dei sottotipi all'interfaccia supertipo, il che significa che deve essere modificato quando vengono aggiunti nuovi sottotipi.

+2

A proposito, ci sono mammiferi che depongono le uova. –

+0

"In questo caso, dove si hanno metodi che si applicano solo a sottotipi specifici, il refactoring più promettente sarebbe quello di avere liste separate per gli animali che sono strati di uova e gli animali che sudano". Penso che questo sia il modo in cui dovrò andare, le ulteriori interfacce dovrebbero essere rese reciprocamente esclusive in qualche modo - le interfacce avranno il minor numero possibile di metodi ... – Farrell

+0

e sì, lo so - ed è per questo che ho scelto queste interfacce specificamente per l'esempio [e ho menzionato l'ornitorinco nella domanda] – Farrell

5

L'interfaccia IAnimal (o qualche sua estensione) ha bisogno di un metodo callAllMethods che ciascun implementatore dell'interfaccia possa codificare per eseguire questo compito in modo polimorfo - sembra l'unico approccio OO-sound!

+0

Posso vedere da dove viene questo, ma sfortunatamente non penso che funzionerà per me come in alcuni casi potrei è necessario fare affidamento su oggetti che il metodo chiama return [di tipi specifici], e in altri casi non lo farò e non ha senso che il metodo abbia un tipo di reso non vuoto – Farrell

+0

Tuttavia, ogni caso in cui l'elaborazione è di tipo dipendente deve essere codificato all'interno del tipo (o classi annidate), se si vuole affermare che stai facendo Java OOP; quindi questi diversi "casi"/"altri casi" devono essere presenti come metodi sull'interfaccia. –

+2

Mi piace, ma possiamo sicuramente trovare un nome migliore. "liveAnotherDay()" o qualcosa ... –

1

in C#, si dovrebbe essere in grado di farlo in modo trasparente.

foreach(IEggLayer egglayer in animals) { 
    egglayer.layEgg(); 
} 

foreach(IMammal mammal in animals) { 
    mammal.sweat(); 
} 
+0

Tranne che sta usando java, viene visualizzato, dalla sintassi del ciclo for. –

+0

sfortunatamente sto lavorando in Java, che fino a quando non riesco a capire, non funziona - tipo mismatching – Farrell

+0

(convertito in java) –

0

Perché non hanno metodi aggiunti isAnimal:

public interface IAnimal { 
    bool isEggLayer(); 
    bool isMammal(); 
} 

Quindi è possibile scorrere e proprio interrogare questo booleana ogni volta.

Aggiornamento: Se questo è stato il disegno di un animale, quindi avere una classe che è completamente chiuso è ragionevole, basta chiamare drawVehicle e disegna una corvetta, Cessna, moto, qualunque sia.

Ma questa sembra avere un'architettura non OOP, quindi se l'architettura dell'applicazione non può cambiare, poiché la mia risposta originale non è ben accolta, sembrerebbe che AOP sarebbe la scelta migliore .

Se si mette un'annotazione su ogni classe, si può avere

@IsEggLayer 
@IsMammal 
public class Platypus() implements EggLayer, Mammal { 
... 
} 

Questo sarebbe quindi consentono di creare aspetti che tirare fuori tutte le egglayers e fare tutto ciò le operazioni devono essere fatte.

È inoltre possibile inserire nelle interfacce animali qualsiasi classe aggiuntiva per far funzionare questa idea.

Avrò bisogno di pensare a dove andare da qui, ma ho la sensazione che questa potrebbe essere la soluzione migliore, se una riprogettazione non può essere fatta.

+0

principalmente perché ciò significherebbe dover cambiare IAnimal e tutte le sue implementazioni ogni volta che aggiungo un tipo con nuove funzionalità, invece di creare il nuovo ADT e solo bisogno di aggiungere il controllo nel ciclo. – Farrell

+0

Nessun vantaggio reale nel fare il typechecking all'esterno - infrange ancora i principi fondamentali di OOP (qui, l'interfaccia, così come il codice chiamante, DEVE ENTRAMBI conoscere tutti i possibili gruppi di implementazioni, quindi è discutibilmente ancora peggio). –

+0

Non sono sicuro che se non sia ben accolto è quello che intendevo con il mio commento. Più che la risposta originale stava praticamente cambiando una forma di controllo dei tipi per un'altra, che per quanto posso dire comporterebbe più manutenzione quando vengono introdotte nuove interfacce e funzionalità – Farrell

1

Penso che il modo di pensare a questa domanda sia: che cosa fa il ciclo? Il ciclo ha uno scopo e sta cercando di fare qualcosa con quegli oggetti. Quel qualcosa può avere un metodo sull'interfaccia IAnimal e le implementazioni possono sudare o deporre le uova secondo necessità.

In termini di problema con il valore restituito, verrà restituito nulla, non si può fare nulla se si condividono i metodi. Non vale la pena di lanciare all'interno di un ciclo per evitare un ulteriore ritorno null; per soddisfare il compilatore. È possibile, tuttavia, rendere più espliciti che utilizzano farmaci generici:

public interface IAnimal<R> { 

     public R generalMethod(); 

    } 

    public interface IEggLayer extends IAnimal<Egg> { 

     public Egg generalMethod(); //not necessary, but the point is it works. 

    } 

    public interface IMammal extends IAnimal<Void> { 

     public Void generalMethod(); 

    } 

Dal tuo commento in cui vi preoccupate per il tipo di ritorno, è possibile ottenere il tipo di ritorno e inviare ad un metodo factory che esamina il tipo e restituisce qualcosa generico che viene sublassato al tipo specifico e agisce in tal senso.

0

Ci sono molti modi per farlo. Esattamente che è il più appropriato dipende dal tuo contesto. Ho intenzione di suggerire l'introduzione di un ulteriore strato di riferimento indiretto. Questo risolve molti problemi. Preferisco i disegni che evitano l'ereditarietà dell'interfaccia (se fossi presidente di una lingua, lo proibirei).

Non credo che layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant() sia un ottimo metodo per eseguire il poligono con Animal. Pertanto, anziché aggiungere ogni Animal direttamente a animals, racchiudile in un singolo wrapper. Dico "wrapper" come nome generico - l'operazione casuale che stiamo cercando di eseguire non ha alcun senso.

private final List<AnimalWrapper> animals = 
    new ArrayList<AnimalWrapper>(); 

public void doStuff() { 
    for (AnimalWrapper animal : animals) { 
     animal.doStuff(); 
    } 
} 

Quindi abbiamo bisogno di un modo per aggiungere i wrapper. Qualcosa del tipo:

public void addPlatypus(final Platypus platypus) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     platypus.sweat(); 
     platypus.layEgg(); 
    }}); 
} 

Se si tenta di scrivere questi wrapper senza un contesto sufficiente, si mette nei guai. Questi richiedono che venga selezionato quello corretto sul sito di chiamata. Potrebbe essere fatto sovraccaricando, ma questo ha dei pericoli.

/*** Poor context -> trouble ***/ 

public void addNormalMamal(final Mamal mamal) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     mamal.sweat(); 
    }}); 
} 
public void addNormalEggLayer(final EggLayer eggLayer) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     eggLayer.layEgg(); 
    }}); 
} 
public <T extends Mamal & EggLayer> void addMamalEggLayer(final T animal) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     animal.sweat(); 
     animal.layEgg(); 
    }}); 
} 
Problemi correlati