重载方法,将子类作为参数,但将该方法作为超类调用



我有一个抽象类Animal,它有两个扩展类DogCat

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

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

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

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
}

从本质上讲,每种方法都充当一个"选择器",该方法接收动物的type

当如上所述尝试时,我得到以下编译错误:

错误:找不到适用于doSomething(动物)的方法

我知道我可以使用类似instanceOfa.getClass().equals(type)的东西,但我读到这是一种糟糕的做法。这个问题与另一个问题略有不同,因为我想要两个独立的方法,每个方法都有不同的参数。

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

将方法移动到Abstract类中,这样每个实现都必须制作自己的方法版本。

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

然后在你上面提到的其他课程中,你可以做以下事情:

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

不要在客户端方法中迭代并调用doSomething(),而是将doSometing()作为抽象类中的一个方法,这样你就可以在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
}
}

Yes instanceof和getclass会使您的类依赖于特定的类型,使用这样的做法并不太好。

但是,通过使用接受DogCat的方法,您将再次依赖于具体的具体实现。

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

这样,Dog类中就有了与Dog相关的逻辑,Cat类中就没有了与Cat相关的逻辑。这将有助于尊重OCP和SRP(开放-关闭和单一责任原则)。

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

您需要一个双重调度:一个由java提供的运行时类型+一个必须实现的运行时参数类型。访问者模式就是这样做的一种方式。

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

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子类,而These只需要有一种方法来调用访问者方法,而不需要任何公共方法
因此,一个改进是引入Visitor接口,将Animal对访问者的了解限制为doSomething()

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

因此,让MyClassWithSomeAnimals实现它,并将Visitable类/具体类更改为使用它:

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

Java中有一条重要规则定义了继承、接口和抽象类的大部分工作方式-超类引用变量可以容纳子类对象。因此,有几种方法可以克服您的错误。

  1. 如果您的DogCat类是Animal抽象类的具体类,那么您只需定义一个doSomething(Animal animal),该方法就可以工作了
  2. 您可以尝试使doSomething方法通用。例如public <T> void doSomething(<T extends Animal> animal)
  3. 或者,您可以在Animal中定义一个doSomething抽象方法,并让子类为其提供自己的实现,正如Andrew S.在上面的回答中所建议的那样

最新更新