【问题标题】:Java visitor patternJava 访问者模式
【发布时间】:2016-02-05 09:22:28
【问题描述】:

我尝试将访问者模式与扩展类一起使用。我有动物类的列表,每个类都是不同的动物。当我打电话给访问者时,它只会执行谈话(动物 a),而不是对象的具体实例。见下文:

class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class Poodle extends Dog {}

class Visitor {
    public void talk(Animal a) { System.out.println("?"); }
    public void talk(Cat a) { System.out.println("Meow"); }
    public void talk(Dog a) { System.out.println("bark"); }
    public void talk(Poodle a) { System.out.println("Arf"); }
}    

public class Demo{

    public static void main(String []args){
        Visitor visitor = new Visitor();
        Animal list[] = { new Cat(), new Dog(), new Poodle() };

        for (Animal a : list)
            visitor.talk(a);
    }
}

输出是:

?
?
?

虽然我期待:

Meow
bark
Arf 

知道如何在不将许多 instanceof 放入单个 talk() 方法的情况下实现访问者吗?

【问题讨论】:

  • 你使用了什么样的访问者模式描述?
  • 如果你想以 Animals 的形式循环遍历列表而不覆盖函数,你应该检查instanceof 然后转换对象。例如在您的列表中if(a instanceof cat){ visitor.talk(((Cat)a).talk()}。但我强烈建议您只覆盖该方法,因为您已经在使用继承
  • 作为一个讨厌访问者模式的人,我问,为什么不只依赖典型的接口/类层次结构而不是强加一个模式呢?

标签: java visitor-pattern


【解决方案1】:

Visitor pattern 的一个关键元素是double-dispatch:您需要从实际子类调用访问者方法

abstract class Animal {
  abstract void accept(Visitor v);
}

class Dog extends Animal {
  @Override void accept(Visitor v) { v.talk(this); }
}

// ... etc for other animals.

然后,您将访问者传递给动物,而不是将动物传递给访问者:

for (Animal a : list)
  a.accept(visitor);

原因是编译器选择了Visitor 上的重载来调用——它在运行时没有被选中。编译器只知道aAnimal,所以它知道可以安全调用的唯一方法是Visitor.visit(Animal) 重载。

【讨论】:

  • 呃,我的头好痛。请告诉我,我不是唯一一个认为这是对模式的滥用的人。就实施而言,您的示例很好,但是这些关系没有相互访问的事实是否使它变得混乱?与冲突不同,例如,visitable 和访问者的类型涉及 matter,此示例不需要与 talk 的访问者交互。 OP 问题的解决方案似乎是基于多态的,而不是基于模式的。 +1 表明它在这种情况下很有用。
  • 你说得对,这个简单的例子有点矫枉过正。
  • ...但是一个简单的示例也可以很容易地显示它的错误之处,以便更容易理解如何在适合的地方正确实现它。
  • 同意;一个简单的例子确实很有帮助。但是我看到许多发帖者都在努力理解多态性,例如,由于用例不佳,部分/全部。很难不将此视为一个示例,它超越了 poly 的力量,同时使访问者更难以欣赏。这当然不是你的错:)
【解决方案2】:

访问者模式基本上是一种多对多的类行为解析机制。为了对您的动物示例有用/适用,我们需要添加一个访客,正如 Andy Turner 所指出的那样。

邀请“动物训练师”作为访问者怎么样?不同的驯兽师可以让不同的动物以不同的方式说话(或不说话)。

首先,实体(动物界面)。我喜欢把它想象成“被演员行动”的“事物”:

public interface IAnimal {

    public String getAnimalName();

    // aka...  public void accept(Visitor v)
    public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer);
}

然后让我们定义一个访问者类型(动物训练师界面)。我喜欢将访问者视为对各种“事物”(实体)进行操作的“演员”:

public interface IAnimalTrainer {

    public String getTrainerName();

    //aka... public void visit(Dog dog);
    public void animalTrainerMakesAnimalSpeak(Dog dog);
    public void animalTrainerMakesAnimalSpeak(Cat cat);
    public void animalTrainerMakesAnimalSpeak(Poodle poodle);
}

现在让我们创建动物(实现 IAnimal 接口):

猫:

public class Cat implements IAnimal {

    private String animalName;

    @Override
    public String getAnimalName() {
        return animalName;
    }
    public void setAnimalName(String animalName) {
        this.animalName = animalName;
    }

    public Cat(String animalName) {
        this.animalName = animalName;
    }
    public Cat() {
        // Default constructor
    }

    @Override
    public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
        animalTrainer.animalTrainerMakesAnimalSpeak(this);
    }
}

狗:

public class Dog implements IAnimal {

    private String animalName;

    @Override
    public String getAnimalName() {
        return animalName;
    }
    public void setAnimalName(String animalName) {
        this.animalName = animalName;
    }

    public Dog(String animalName) {
        this.animalName = animalName;
    }
    public Dog() {
        // Default constructor
    }

    @Override
    public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
        animalTrainer.animalTrainerMakesAnimalSpeak(this);
    }
}

贵宾犬:

public class Poodle extends Dog {

    public Poodle(String animalName) {
        super(animalName);
    }
    public Poodle() {
        super();
    }

    @Override
    public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
        animalTrainer.animalTrainerMakesAnimalSpeak(this);
    }
}

现在让我们创建驯兽师 Phil 和 Jack(实现 IAnimalTrainer 接口):

驯兽师菲尔:

public class AnimalTrainerPhil implements IAnimalTrainer {

    private String trainerName = "Phil";

    @Override
    public String getTrainerName() {
        return trainerName;
    }
    public void setTrainerName(String trainerName) {
        this.trainerName = trainerName;
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Dog dog) {
        System.out.println(
                "Animal trainer " 
                + getTrainerName()
                + " gets " 
                + dog.getAnimalName() 
                + " the dog to say: BARK!!");
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Cat cat) {
        System.out.println(
                "Animal trainer "
                + getTrainerName()
                + " gets " 
                + cat.getAnimalName() 
                + " the cat to say: MEOW!!");
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Poodle poodle) {
        animalTrainerMakesAnimalSpeak((Dog)poodle);
    }
}

驯兽师杰克:

public class AnimalTrainerJack implements IAnimalTrainer {

    private String trainerName = "Jack";

    @Override
    public String getTrainerName() {
        return trainerName;
    }
    public void setTrainerName(String trainerName) {
        this.trainerName = trainerName;
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Dog dog) {
        System.out.println(
                "Animal trainer " 
                + getTrainerName()
                + " gets " 
                + dog.getAnimalName() 
                + " the dog to say: Bark bark.");
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Cat cat) {
        System.out.println(
                "Animal trainer " 
                + getTrainerName()
                + " gets " 
                + cat.getAnimalName() 
                + " the cat to say: Meoooow.");
    }

    @Override
    public void animalTrainerMakesAnimalSpeak(Poodle poodle) {
        System.out.println(
                "Animal trainer " 
                + getTrainerName()
                + " gets " 
                + poodle.getAnimalName() 
                + " the poodle to say: Yip! Yip!");
    }
}

现在让我们把这一切放在一起,让所有动物训练师(Phil 和 Jack)通过 Manager 类让所有动物(猫、狗、贵宾犬)说话:

public class ManagerOfAnimalTrainersAndAnimals {

    public static void main(String[] args) {

        ArrayList<IAnimal> allAnimals = new ArrayList<>();
        allAnimals.add(new Dog("Henry"));
        allAnimals.add(new Cat("Priscilla"));
        allAnimals.add(new Poodle("Spike"));

        ArrayList<IAnimalTrainer> allAnimalTrainers = new ArrayList<>();
        allAnimalTrainers.add(new AnimalTrainerPhil());
        allAnimalTrainers.add(new AnimalTrainerJack());

        // Allow all animal trainers to get each animal to speak
        for (IAnimalTrainer animalTrainer : allAnimalTrainers) {
            for (IAnimal animal : allAnimals) {
                animal.allowAnimalTrainerToMakeMeSpeak(animalTrainer);
            }
        }
    }
}

这是输出:

Animal trainer Phil gets Henry the dog to say: BARK!! 
Animal trainer Phil gets Priscilla the cat to say: MEOW!! 
Animal trainer Phil gets Spike the dog to say: BARK!! 
Animal trainer Jack gets Henry the dog to say: Bark bark. 
Animal trainer Jack gets Priscilla the cat to say: Meoooow. 
Animal trainer Jack gets Spike the poodle to say: Yip! Yip!

这使得添加新的训练员变得相对容易,这些训练员可以访问特定动物类型的内容(例如,如果您向猫添加爪信息,向狗添加爪信息,...),因为它们对各种动物采取行动。我认为传统的“访问者”示例的问题在于它太模糊且不够具体。出租车的例子也没有为我做。我希望这个例子有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    相关资源
    最近更新 更多