Jak uniknąć dużych poleceń if I instanceof

Zwierzę

public abstract class Animal {
 String name;

 public Animal(String name) {
  this.name = name;
 }

}

Lew

public class Lion extends Animal {

 public Lion(String name) {
  super(name);
  // TODO Auto-generated constructor stub
 }

 public void roar() {
  System.out.println("Roar");
 }
}

Jeleń

public class Deer extends Animal {

 public Deer(String name) {
  super(name);
 }

 public void runAway() {
  System.out.println("Running...");
 }

}

TestAnimals

public class TestAnimals {
 public static void main(String[] args) {
  Animal lion = new Lion("Geo");
  Animal deer1 = new Deer("D1");
  Animal deer2 = new Deer("D2");

  List<Animal> li = new ArrayList<Animal>();
  li.add(lion);
  li.add(deer1);
  li.add(deer2);
  for (Animal a : li) {
   if (a instanceof Lion) {
    Lion l = (Lion) a;
    l.roar();
   }
   if (a instanceof Deer) {
    Deer l = (Deer) a;
    l.runAway();
   }

  }
 }
}

Czy jest lepszy sposób na iterację listy bez konieczności rzucania ?W powyższym przypadku wydaje się, że jest ok, ale jeśli masz wiele rozszerzeń klasy bazowej, to będziemy potrzebować tyle if block too.Is czy istnieje wzór lub zasada, aby rozwiązać ten problem ?

Author: aioobe, 2010-10-14

9 answers

Eleganckim sposobem unikania instanceof bez wymyślania nowej sztucznej metody w klasie bazowej (o nieopisowej nazwie, takiej jak performAction lub doWhatYouAreSupposedToDo) jest użycie wzorca odwiedzającego . Oto przykład:

Zwierzę

import java.util.*;

abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public abstract void accept(AnimalVisitor av);  // <-- Open up for visitors.

}

Lew i Jeleń

class Lion extends Animal {
    public Lion(String name) {
        super(name);
    }
    public void roar() {
        System.out.println("Roar");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this);                            // <-- Accept and call visit.
    }
}


class Deer extends Animal {

    public Deer(String name) {
        super(name);
    }

    public void runAway() {
        System.out.println("Running...");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this);                            // <-- Accept and call visit.
    }

}

Gość

interface AnimalVisitor {
    void visit(Lion l);
    void visit(Deer d);
}

class ActionVisitor implements AnimalVisitor {

    public void visit(Deer d) {
        d.runAway();
    }

    public void visit(Lion l) {
        l.roar();
    }
}

TestAnimals

public class TestAnimals {
    public static void main(String[] args) {
        Animal lion = new Lion("Geo");
        Animal deer1 = new Deer("D1");
        Animal deer2 = new Deer("D2");

        List<Animal> li = new ArrayList<Animal>();
        li.add(lion);
        li.add(deer1);
        li.add(deer2);
        for (Animal a : li)
            a.accept(new ActionVisitor());         // <-- Accept / visit.
    }
}
 30
Author: aioobe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-15 14:21:40

Zwierzę

public abstract class Animal {
 String name;

 public Animal(String name) {
  this.name = name;
 }

 public abstract void exhibitNaturalBehaviour();

}

Lew

public class Lion extends Animal {

 public Lion(String name) {
  super(name);
 }

 public void exhibitNaturalBehaviour() {
  System.out.println("Roar");
 }
}

Jeleń

public class Deer extends Animal {

 public Deer(String name) {
  super(name);
 }

 public void exhibitNaturalBehaviour() {
  System.out.println("Running...");
 }

}

TestAnimals

public class TestAnimals {
 public static void main(String[] args) {

  Animal[] animalArr = {new Lion("Geo"), new Deer("D1"), new Deer("D2")};
  for (Animal a : animalArr) {
     a.exhibitNaturalBehaviour();    
  }

 }
}
 12
Author: Adeel Ansari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-08 17:25:12

Tak podaj metodę o nazwie action() w klasie abstrakcyjnej, zaimplementuj ją w obu klasach potomnych, jedna będzie ryczeć druga ucieknie

 5
Author: Jigar Joshi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-14 07:02:13

Okazuje się, że instanceof jest szybszy niż wzór odwiedzającego przedstawiony powyżej; myślę, że powinno to nas zadawać pytanie, czy wzór odwiedzającego jest naprawdę bardziej elegancki niż instanceof, gdy robi to samo wolniej z większą liczbą linii kodu?

Oto mój test. Porównałem metody 3: wzorzec odwiedzającego powyżej, instanceof i wyraźne pole typu w zwierzęciu.

OS: Windows 7 Enterprise SP1, 64-bit
Procesor: Intel(r) core(TM) i7 CPU 860 @ 2.80 GHz 2.93 GHz
RAM: 8.00 GB
JRE: 1.7.0_21-b11, 32-bit

import java.util.ArrayList;
import java.util.List;

public class AnimalTest1 {
    public static void main(String[] args) {
        Animal lion = new Lion("Geo");
        Animal deer1 = new Deer("D1");
        Animal deer2 = new Deer("D2");

        List<Animal> li = new ArrayList<Animal>();
        li.add(lion);
        li.add(deer1);
        li.add(deer2);

        int reps = 10000000;

        long start, elapsed;

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li)
                a.accept(new ActionVisitor()); // <-- Accept / visit.
        }
        elapsed = System.nanoTime() - start;

        System.out.println("Visitor took " + elapsed + " ns");

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li) {
                if (a instanceof Lion) {
                    ((Lion) a).roar();
                } else if (a instanceof Deer) {
                    ((Deer) a).runAway();
                }
            }
        }
        elapsed = System.nanoTime() - start;

        System.out.println("instanceof took " + elapsed + " ns");

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li) {
                switch (a.type) {
                case Animal.LION_TYPE:
                    ((Lion) a).roar();
                    break;
                case Animal.DEER_TYPE:
                    ((Deer) a).runAway();
                    break;
                }
            }
        }
        elapsed = System.nanoTime() - start;

        System.out.println("type constant took " + elapsed + " ns");
    }
}

abstract class Animal {
    public static final int LION_TYPE = 0;
    public static final int DEER_TYPE = 1;

    String name;
    public final int type;

    public Animal(String name, int type) {
        this.name = name;
        this.type = type;
    }

    public abstract void accept(AnimalVisitor av); // <-- Open up for visitors.
}

class Lion extends Animal {
    public Lion(String name) {
        super(name, LION_TYPE);
    }

    public void roar() {
        // System.out.println("Roar");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this); // <-- Accept and call visit.
    }
}

class Deer extends Animal {

    public Deer(String name) {
        super(name, DEER_TYPE);
    }

    public void runAway() {
        // System.out.println("Running...");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this); // <-- Accept and call visit.
    }

}

interface AnimalVisitor {
    void visit(Lion l);

    void visit(Deer d);
}

class ActionVisitor implements AnimalVisitor {

    public void visit(Deer d) {
        d.runAway();
    }

    public void visit(Lion l) {
        l.roar();
    }
}

Wyniki testu:

Odwiedzin 920842192 ns
instanceof wziął 511837398 NS
Typ stała wzięła 535296640 NS

Ten wzorzec odwiedzających wprowadza 2 dodatkowe wywołania metod, które są niepotrzebne w instanceof. Pewnie dlatego jest wolniejszy.

Nie tylko wydajność jest brana pod uwagę, ale zauważ, że 2 instanceofs są szybsze niż nawet 2-case switch statement. Wielu ludzi się martwiło. o wydajności instanceof, ale to powinno położyć zmartwienie na odpoczynek.

Jako programista Javy czuję się sfrustrowany, gdy ludzie mają dogmatyczne podejście do unikania użycia instanceof, ponieważ kilka razy w mojej pracy chciałem posprzątać lub napisać nowy czysty kod za pomocą instanceof, ale współpracownicy / przełożeni nie aprobowali tego podejścia, ponieważ mniej lub bardziej ślepo zaakceptowali ideę, że instanceof nie powinien być nigdy używany. Czuję się sfrustrowany, ponieważ ten punkt jest często napędzany do domu z przykładami zabawek, które nie odzwierciedlają prawdziwych obaw biznesowych.

Ilekroć zajmujesz się modularnym projektowaniem oprogramowania, zawsze będą chwile, kiedy decyzje zależne od typu będą musiały być odizolowane od typów, o których mowa, tak aby typy miały jak najmniej zależności.

Ten wzorzec odwiedzających nie łamie modułowości, ale nie jest lepszą alternatywą dla instanceof.

 3
Author: Andy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-07 16:18:50

Jeśli twoja metoda nie jest polimorficzna, nie możesz obejść się bez gipsu. Aby uczynić ją polimorficzną, zadeklaruj metodę w klasie bazowej i nadpisaj ją w klasach potomnych.

 2
Author: Petar Minchev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-14 07:03:20

Tutaj masz List zwierząt. Zazwyczaj, gdy masz listę obiektów, wszystkie te obiekty muszą być w stanie zrobić to samo bez rzucania.

Więc najlepsze dwa rozwiązania to:

  • o wspólnej metodzie dla dwóch konkretnych klas (tak zdefiniowanych jako abstract w Animal)
  • oddzielić Lion od Deer Od początku i mieć dwie różne listy.
 2
Author: Colin Hebert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-14 07:10:36

Obsługa dopasowywania wzorców w języku eliminuje potrzebę brzydkiego wzorca odwiedzających.

Zobacz ten kod Scali na przykład:

abstract class Animal(name: String)

class Lion(name: String) extends Animal(name) {
  def roar() {
    println("Roar!")
  }
}

class Deer(name: String) extends Animal(name) {
  def runAway() {
    println("Running!")
  }
}

object TestAnimals {
  def main(args: Array[String]) {
    val animals = List(new Lion("Geo"), new Deer("D1"), new Deer("D2"))
    for(animal <- animals) animal match {
      case l: Lion => l.roar()
      case d: Deer => d.runAway()
      case _       => ()
    }
  }
}
 2
Author: Eric Grindt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-15 03:49:19

Rozważ dodanie interfejsu dla akcji (ryk, Ucieczka, itp.), który jest ustawiany na zwierzęciu w konstruktorze. Następnie zastosuj abstrakcyjną metodę, taką jak act () NA klasie zwierząt, która jest nazywana podobną do tej, którą ma Adeel.

Pozwoli to na zamianę akcji na działanie za pomocą pola w dowolnym momencie.

 1
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-14 07:17:21

Najprostszym podejściem jest, aby Klasa super zaimplementowała domyślne zachowanie.

public enum AnimalBehaviour { 
     Deer { public void runAway() { System.out.println("Running..."); } },
     Lion { public void roar() { System.out.println("Roar"); } }
     public void runAway() { } 
     public void roar() { }
 } 

 public class Animal {
     private final String name;
     private final AnimalBehaviour behaviour;
     public Animal(String name, AnimalBehaviour behaviour) {
         this.name = name;
         this.behaviour = behaviour;
     }
     public void runAway() { behaviour.runAway(); } 
     public void roar() { behaviour.roar(); }
  }

 public class TestAnimals { 
   public static void main(String... args) { 
     Animal[] animals = { 
       new Animal("Geo", AnimalBehaviour.Lion), 
       new Animal("Bambi", AnimalBehaviour.Deer), 
       new Animal("D2", AnimalBehaviour.Deer) 
     }; 

     for (Animal a : animals) {
       a.roar(); 
       a.runAway(); 
     } 
   }
 }
 1
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-15 06:09:32