【问题标题】:Overload method with subclasses as parameters but have the method called as the superclass以子类为参数重载方法,但将方法称为超类
【发布时间】:2018-08-23 16:04:09
【问题描述】:

我有一个抽象类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 中调用它们。如下:

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 of animal 的“选择器”。

如上尝试,我得到以下编译错误:

错误:找不到适合 doSomething(Animal) 的方法

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

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

【问题讨论】:

  • 只在抽象类中有doSomething 方法,所以DogCat 必须实现它。然后,在循环时,只需调用doSomething,它就会调用特定于实现的对象。
  • 如果有人写Bird extends Animal 并将Bird bird = new Bird() 放入列表中。应该调用哪个方法?
  • doSomething() 应该在Anmial 中,并在每个子类中被覆盖。所以不是doSomething(a),而是a.doSomething()
  • 这是不可能的,因为要调用的方法是在编译时选择的。

标签: java overloading subclass extending-classes


【解决方案1】:

将方法移动到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();
      }
   }

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 请注意,只有代码的答案通常会被否决;请说明如何解决问题中所述的问题。
    • 添加描述和代码 + 在你的答案中包含 Animal 定义了 doSomething()。在您的代码中,它看起来 Dog 和 Cat 有 doSomething 方法,但它没有说明 Animal 类型引用如何调用它。同样在您的问题中,让 Dog 和 Cat 实现 Animal 接口。
    • 知道了。添加它。谢谢
    【解决方案3】:

    是的,instanceof 和 getclass 会使您的类依赖于特定类型,并且使用此类做法不太好。

    但是通过让方法接受DogCat,你最终还是取决于具体的具体实现。

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

    这样,您将在 Dog 类中拥有与 Dog 相关的逻辑,在 Cat 类中拥有与 Cat 相关的逻辑,而不是在任何外部不同的类中。这将有助于尊重 OCP 和 SRP(开放式关闭和单一职责原则)。

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

    【讨论】:

    • 这与@Dan 的代码示例一起帮助最大。谢谢。我最初不想将 doSomething() 方法移动到 Dog 和 Cat 类中,因为就类的实际操作而言,它并没有太多逻辑意义,但我将重构以更好地反映这一点。跨度>
    【解决方案4】:

    您需要双重调度: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 子类,而这些子类只需要有一种方法来调用访问者方法,而不是任何公共方法。
    所以一个改进是引入一个Visitor接口来限制doSomething()动物对访问者的了解:

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

    所以让MyClassWithSomeAnimals 实现它并更改可访问类/具体类以使用它:

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-13
        • 1970-01-01
        • 1970-01-01
        • 2010-09-28
        相关资源
        最近更新 更多