以子类作为参数的重载方法,但将方法称为超类

Overload method with subclasses as parameters but have the method called as the superclass

我有一个摘要 class Animal,有两个扩展 classes,DogCat

public abstract class Animal {
...
}
public class Dog extends Animal {
...
}
public class Cat extends Animal {
...
}

在另一个 class 中,我有一个包含 CatDog 实例的 ArrayList<Animal> animals

在带有 ArrayList<Animal> 的 class 中,我希望能够使用 Dog dCat c 作为参数重载方法 doSomething() ,但仍然可以从 Animals 的 ArrayList 中调用它们。如下:

public void aMethod(){
    ArrayList<Animals> animals = getTheAnimals();
    for (Animal a : animals){
        doSomething(a);
    }
}
public void doSomething(Dog a){
//a is an object of type dog
}
public void doSomething(Cat a){
//a is an object of type cat
}

本质上,每个方法都充当 'selector' 方法接收 type 动物。

按上述方式尝试时,出现以下编译错误:

error: no suitable method found for doSomething(Animal)

我知道我可以使用 instanceOfa.getClass().equals(type) 之类的东西,但我了解到这是不好的做法。 这个问题与 this other question 略有不同,因为我想要两个单独的方法,每个方法都有不同的参数。

编辑: 我将避免使用访问者模式,因为我认为它不太适合我的实际实现。将把 doSomething() 方法移动到每个 class 中,并重构以确保这在逻辑上有意义(每个 class 执行它应该负责的操作)。谢谢。

不要在您的客户端方法中迭代并调用 doSomething(),而是将 doSomething() 作为您抽象中的方法 class,这样您就可以在 Cat 或 Dog 中更改您想要的方式。

public void aMethod(){
  ArrayList<Animals> animals = getTheAnimals();
    for (Animal a : animals){
       a.doSomething();
     }
  }

abstract Animal {
   doSomething();
}

Dog extends Animal {


  doSomething() {
    //Dog stuff
  }

}

Cat extends Animal {

   doSomething() {
     //cat stuff
   }

}

将方法移入 Abstract class 因此每个实现都必须制作自己的方法版本。

public abstract class Animal {

  public abstract void doSomething();
}

public class Dog extends Animal {
   public void doSomething(){
       System.out.println("Bark");
   }
}

public class Cat extends Animal {
   public void doSomething(){
       System.out.println("Meow");
   }
}

然后在您上面提到的另一个Class中您可以执行以下操作:

  public void vocalize(){
      ArrayList<Animals> animals = getTheAnimals();
      for (Animal a : animals){
         a.doSomething();
      }
   }

您需要双重分派:java 提供的运行时类型 + 您必须实现的运行时参数类型。访问者模式是一种做到这一点的方法。

引入一个Visitable接口,在Animal中实现它以使其接受访问者。
然后介绍一个访客。您可以根据您的需要使用或不使用接口来实现它。
保持代码实际逻辑的简单版本(通过将访问者耦合到当前 class):

public interface Visitable{
   void accept(MyClassWithSomeAnimals myClassWithSomeAnimals);
}

public abstract class Animal implements Visitable{
...
}

public class Dog extends Animal {
...
  void accept(MyClassWithSomeAnimals myClassWithSomeAnimals){
     myClassWithSomeAnimals.doSomething(this);
  }

}
public class Cat extends Animal {
...
  void accept(MyClassWithSomeAnimals myClassWithSomeAnimals){
     myClassWithSomeAnimals.doSomething(this);
  }
}

并以这种方式更新 MyClassWithSomeAnimals

public void aMethod(){
    ArrayList<Animals> animals = getTheAnimals();
    for (Animal a : animals){
         a.accept(this);
    }
}

此解决方案有效,但有一个缺点。它将 MyClassWithSomeAnimals 暴露给 Animal 子 classes 而这些只需要有一种方法来调用访问者方法,而不是任何 public 方法。
因此,一种改进是引入一个 Visitor 界面,以将 Animal 对访客的了解限制为 doSomething()

public interface Visitor{
  void doSomething(Dog dog);
  void doSomething(Cat cat);
}

因此 MyClassWithSomeAnimals 实现它并更改 Visitable class/concrete classes 以使用它:

public interface Visitable{
  void accept(Visitor visitor);
}

public class Dog extends Animal {
...
  void accept(Visitor visitor){
     visitor.doSomething(this);
  }

}
public class Cat extends Animal {
...
  void accept(Visitor visitor){
     visitor.doSomething(this);
  }
}

是的,instanceof 和 getclass 会使您的 class 依赖于特定类型,使用这种做法不太好。

但是通过接受 DogCat 的方法,您再次最终取决于具体的具体实现。

更好的方法是在 Animal 接口 performAction() 中定义方法。在 DogCat 中提供实现。现在迭代您的动物列表,只需调用 performAction() 并根据实际实例以多态方式调用 Dog 或 Cat 实现的方法。以后即使在代码中添加了 Animal 的另一个实现,您也不需要修改代码。

这样你就会在 Dog class 中有 Dog 相关的逻辑,在 Cat class 中有 Cat 相关的逻辑,而不是在任何外部不同的 class 中。这将有助于遵守 OCP 和 SRP(开闭和单一职责原则)。

此外,如果您希望稍后执行行为并且您的情况是这样的,您知道您的实现是固定的并且希望使用代码稍后注入行为那么我会推荐 Visitor 模式,但我仍然觉得这不应该被滥用,(设计模式通常不被使用但被滥用)有时我们在不需要时强制使用模式。访问者模式使用多态将第一个调用分派到特定实例,从那里第二个分派调用 class 中实现访问者接口的重载方法。 (名称可以不同,我提到 Visitor 暗示 class 实现了访问者的概念)。仅在需要时才采用类似实现的访问者模式,而基于多态的常规方法不适合这种情况。

Java 中有一个重要的规则,它定义了继承、接口和抽象的大部分工作方式 class - 超级class 引用变量可以包含子class object.Hence,有几种方法可以克服你的错误。

  1. 如果您的 DogCat class 是 Animal 摘要 class 的具体 class,那么您可以简单地定义一个 doSomething(Animal animal),该方法将起作用。
  2. 您可以尝试使 doSomething 方法通用。如 public <T> void doSomething(<T extends Animal> animal)
  3. 或者您可以在 Animal 中定义一个 doSomething 抽象方法,并让子classes 为其提供它们自己的实现,正如 Andrew S. 在上述回答中所建议的那样。