Как избежать больших if-операторов и instanceof - PullRequest
16 голосов
/ 14 октября 2010

Животное

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

  }
 }
}

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

Ответы [ 9 ]

31 голосов
/ 14 октября 2010

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

Животное

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

}

Посетитель

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.
    }
}
12 голосов
/ 14 октября 2010

Animal

public abstract class Animal {
 String name;

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

 public abstract void exhibitNaturalBehaviour();

}

Lion

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

 }
}
5 голосов
/ 14 октября 2010

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

2 голосов
/ 07 мая 2013

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

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

ОС: Windows 7 Enterprise SP1, 64-разрядная
Процессор: Intel (R) Core (TM) i7 CPU 860 @ 2,80 ГГц 2,93 ГГц
Оперативная память: 8,00 ГБ
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 instanceofs быстрее, чем даже двухзначный оператор переключения. Многие люди беспокоятся о производительности instanceof, но это должно положить конец беспокойству.

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

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

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

2 голосов
/ 14 октября 2010

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

См. Этот код 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 _       => ()
    }
  }
}
2 голосов
/ 14 октября 2010

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

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

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

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

1 голос
/ 15 октября 2010

Самый простой подход - заставить суперкласс реализовать поведение по умолчанию.

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 голос
/ 14 октября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...