Подтвердить что ты не робот

Как избежать больших if-утверждений и instanceof

Animal

public abstract class Animal {
 String name;

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

}

Лев

public class Lion extends Animal {

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

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

Олень

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();
   }

  }
 }
}

Есть ли лучший способ итерации по списку без использования? В приведенном выше примере это выглядит нормально, но если у вас много расширений базового класса, тогда нам понадобится так много блоков if.Is там дизайн шаблон или принцип решения этой проблемы?

4b9b3361

Ответ 1

Изящный способ избежать instanceof, не изобретая какой-либо новый искусственный метод в базовом классе (с не описательным именем, например performAction или doWhatYouAreSupposedToDo), должен использовать шаблон посетителя. Вот пример:

Animal

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.

}

Лев и Олень

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.
    }

}

Visitor

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.
    }
}

Ответ 2

Animal

public abstract class Animal {
 String name;

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

 public abstract void exhibitNaturalBehaviour();

}

Лев

public class Lion extends Animal {

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

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

Олень

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();    
  }

 }
}

Ответ 3

Да дайте метод, называемый action() в абстрактном классе, реализуйте его в обоих дочерних классах, один будет рев, другой будет бежать

Ответ 4

Оказывается, что instanceof быстрее, чем шаблон посетителя, представленный выше; Я думаю, что это должно поставить нас под вопрос, является ли шаблон посетителя более элегантным, чем instanceof, когда он делает то же самое медленнее с большим количеством строк кода?

Вот мой тест. Я сравнил 3 метода: шаблон посетителя выше, instanceof и явное поле типа в Animal.

ОС: Windows 7 Enterprise SP1, 64-бит
Процессор: Intel (R) Core (TM) i7 CPU 860 @2,80 ГГц 2,93 ГГц
Оперативная память: 8.00 GB
JRE: 1.7.0_21-b11, 32-бит

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();
    }
}

Результаты тестирования:

Посетитель принял 920842192 нс
instanceof взял 511837398 нс
тип константы занял 535296640 нс

Этот шаблон посетителя вводит 2 дополнительных вызова метода, которые не нужны с instanceof. Вероятно, поэтому он медленнее.

Не то, что производительность является единственным соображением, но обратите внимание на то, как 2 экземпляра быстрее, чем даже оператор switch с двумя случаями. Множество людей беспокоились о производительности экземпляра, но это должно поправить беспокойство.

Как разработчик Java, я чувствую себя расстроенным, когда у людей есть догматическое отношение к тому, чтобы избегать использования instanceof, потому что в моей работе было несколько раз, я хотел очистить или написать новый чистый код с помощью instanceof, но сотрудники/начальники не одобряли этот подход, потому что они более или менее слепо приняли идею о том, что instanceof никогда не должен использоваться. Я чувствую себя расстроенным, потому что этот момент часто приводит домой с примерами игрушек, которые не отражают реальных проблем бизнеса.

Всякий раз, когда вы проводите модульную разработку программного обеспечения, всегда будут моменты, когда решения, зависящие от типа, должны быть изолированы от соответствующих типов, чтобы типы имели как можно меньше зависимостей.

Этот шаблон посетителя не нарушает модульность, но не является превосходной альтернативой instanceof.

Ответ 5

Если ваш метод не является полиморфным, вы не можете обойтись без трансляции. Чтобы сделать его полиморфным, объявите метод в базовом классе и переопределите его в классах потомков.

Ответ 6

Здесь у вас есть List животных. Обычно, когда у вас есть список объектов, все эти объекты должны иметь возможность делать то же самое, не будучи литыми.

Итак, лучшие два решения:

  • Имея общий метод для двух конкретных классов (так называемый abstract в Animal)
  • Отделить Lion от Deer с самого начала и иметь два разных списка.

Ответ 7

Поддержка соответствия шаблонов в языке устраняет необходимость уродливого шаблона посетителя.

Смотрите этот код Scala, например:

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 _       => ()
    }
  }
}

Ответ 8

Рассмотрим добавление интерфейса для действия (Roar, Run away и т.д.), который устанавливается на животном в конструкторе. Затем используйте абстрактный метод, например act() в классе Animal, который называется похожим на то, что имеет Adeel.

Это позволит вам поменять местами действия, чтобы действовать через поле в любое время.

Ответ 9

Простейший подход заключается в том, чтобы суперкласс выполнял поведение по умолчанию.

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(); 
     } 
   }
 }