Perché non utilizzare l’operatore instanceof nella progettazione OOP?

È stato ripetutamente detto che l’operatore instanceof non dovrebbe essere usato tranne nel metodo equals (), altrimenti è un cattivo design OOP.

Alcuni hanno scritto che questa è un’operazione pesante, ma sembra che, almeno java, la gestisca abbastanza bene (anche in modo più efficiente rispetto al confronto Object.toString ()).

Qualcuno può spiegare, oppure indirizzarmi su un articolo che spiega perché è un cattivo design?

Considera questo:

Class Man{ doThingsWithAnimals(List animals){ for(Animal animal : animals){ if(animal instanceOf Fish){ eatIt(animal); } else if(animal instanceof Dog){ playWithIt(animal); } } } ... } 

La decisione su cosa fare con l’animale, dipende dall’uomo. I desideri dell’uomo possono anche cambiare di tanto in tanto, decidere di mangiare il Cane e giocare con il Pesce, mentre gli Animali non cambiano.

Se pensi che l’operatore instanceof non sia il design OOP corretto qui, ti preghiamo di dire come lo faresti senza l’instanceof, e perché?

Grazie.

instanceof semplicemente rompe il principio Open / Close. e / o principio di sostituzione di Liskov

Se non siamo abbastanza astratti a causa dell’uso dell’uso, ogni volta che una nuova sottoclass fa un’entrata, il codice principale che raccoglie la logica dell’applicazione potrebbe essere aggiornato. Questo chiaramente non è ciò che vogliamo, dal momento che potrebbe potenzialmente rompere il codice esistente e ridurne la riusabilità.

Pertanto, un uso ottimale del polimorfismo dovrebbe essere preferito rispetto all’uso di base del condizionale.

C’è un buon post sul blog intitolato When Polymorphism Fails che riguarda questo tipo di scenario. Fondamentalmente, hai ragione che dovrebbe spettare Man decidere cosa fare con ogni tipo di Animal . Altrimenti, il codice diventa frammentato e finisci per violare principi come Single Responsibility e Law of Demeter .

Non avrebbe senso avere un codice come ad esempio il seguente:

 abstract class Animal { abstract void interactWith(Man man); } class Fish extends Animal { @Override void interactWith(Man man) { man.eat(this); } } class Dog extends Animal { @Override void interactWith(Man man) { man.playWith(this); } } 

In questo esempio, stiamo mettendo la logica di Man al di fuori della class Man .

Il problema con instanceof è che se hai una grande quantità di Animal s, finirai con un long if-else-if per ognuno di essi. È difficile da mantenere e sobject a errori dove ad esempio viene aggiunto un nuovo tipo di Animal , ma si dimentica di aggiungerlo alla catena if-else-if . ( Il pattern visitor è in parte una soluzione a quest’ultimo problema, perché quando aggiungi un nuovo tipo alla class visitor, tutte le implementazioni smettono di essere compilate e sei costretto ad aggiornarle tutte.)

Tuttavia, possiamo ancora usare il polimorfismo per rendere il codice più semplice ed evitare l’ instanceof .

Ad esempio, se avessimo una routine di alimentazione come:

 if (animal instanceof Cat) { animal.eat(catFood); } else if (animal instanceof Dog) { animal.eat(dogFood); } else if (...) { ... } 

Potremmo eliminare l’ if-else-if con metodi come Animal.eat(Food) e Animal.getPreferredFood() :

 animal.eat(animal.getPreferredFood()); 

Con metodi come Animal.isFood() e Animal.isPet() , l’esempio nella domanda potrebbe essere scritto senza instanceof come:

 if (animal.isFood()) { eatIt(animal); } else if (animal.isPet()) { playWithIt(animal); } 

instanceof è un sistema di escape. Può essere usato per fare cose veramente malvagie, come rendere generici generici o estendere una gerarchia di classi con metodi virtuali ad-hoc che non appaiono mai nell’interfaccia visibile di quelle classi. Entrambe queste cose sono negative per la manutenibilità a lungo termine.

Più spesso, se ti accorgi di voler utilizzare instanceof , significa che c’è qualcosa di sbagliato nel tuo design. Rompere il sistema di tipo dovrebbe essere sempre l’ultima risorsa, non qualcosa da prendere alla leggera.

Non penso che il tuo esempio particolare giustifichi l’uso di instanceof . Il modo orientato agli oggetti per fare ciò è usare il modello di visitatore :

 abstract class Animal { def accept(v: AnimalVisitor) } trait Edible extends Animal { def taste : String def accept(v: AnimalVisitor) = v.visit(this) } trait Pet extends Animal { def growl : String def accept(v: AnimalVisitor) = v.visit(this) } abstract class AnimalVisitor { def visit(e: Edible) def visit(p: Pet) } class EatOrPlayVisitor { def visit(e: Edible) = println("it tastes " + e.taste) def visit(p: Pet) = println("it says: " + p.growl) } class Chicken extends Animal with Edible { def taste = "plain" } class Lobster extends Animal with Edible { def taste = "exotic" } class Cat extends Animal with Pet { def growl = "meow" } class Dog extends Animal with Pet { def growl = "woof" } object Main extends App { val v = new EatOrPlayVisitor() val as = List(new Chicken(), new Lobster(), new Cat(), new Dog()) for (a <- as) a.accept(v) } 

NOTA: Sono consapevole del fatto che Scala ha classi di casi, ma volevo fornire una soluzione generale orientata agli oggetti.