我有一个抽象类
Animal
,有两个扩展类,Dog
和Cat
。
public abstract class Animal {
...
}
public class Dog extends Animal {
...
}
public class Cat extends Animal {
...
}
在另一个课程中,我有一个
ArrayList<Animal> animals
,其中包含 Cat
和 Dog
的实例。
在带有
ArrayList<Animal>
的类中,我希望能够使用 doSomething()
或 Dog d
作为参数重载方法 Cat c
,但仍然能够从 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
}
本质上,每种方法都充当“选择器”,该方法正在接收动物的
type
。
按照上述方法尝试时,出现以下编译错误:
错误:没有找到适合 doSomething(Animal) 的方法
我知道我可以使用
instanceOf
或 a.getClass().equals(type)
之类的东西,但我读到这是不好的做法。
这个问题与另一个问题略有不同,因为我想要两个单独的方法,每个方法都有不同的参数。
编辑:我将避免使用访问者模式,因为我认为它不太适合我的实际实现。将 doSomething() 方法移动到每个类中,并进行重构以确保这具有逻辑意义(每个类执行它应该负责的操作)。谢谢。
不要在客户端方法中迭代并调用 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
}
}
将方法移至
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(){
List<Animal> animals = getTheAnimals();
for (Animal animal : animals){
animal.doSomething();
}
}
是的,instanceof 和 getclass 会让你的类依赖于特定的类型,使用这样的做法不太好。
但是通过接受
Dog
或 Cat
的方法,你最终还是会依赖于具体的具体实现。
更好的方法是在
Animal
接口 performAction()
中定义方法。在 Dog
和 Cat
中提供实施。现在迭代您的动物列表,只需调用 performAction()
并根据实际实例多态地调用 Dog 或 Cat 实现的方法。稍后,即使代码中添加了 Animal 的另一个实现,您也不需要修改代码。
这样,您将在 Dog 类中拥有与 Dog 相关的逻辑,在 Cat 类中拥有与 Cat 相关的逻辑,而不是在任何外部的不同类中。这将有助于遵守 OCP 和 SRP(开放关闭和单一责任原则)。
此外,如果您希望稍后执行行为,并且您的情况是这样的,您知道您的实现是固定的,并且希望使用代码稍后注入行为,那么我会推荐
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
子类,而这些子类只需要有一种方法来调用访问者方法,而不是任何公共方法。Visitor
界面来限制 doSomething()
Animal 对访客的了解:
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 中有一条重要规则定义了继承、接口和抽象类的大部分工作方式 - 超类引用变量可以保存子类对象。因此,有几种方法可以克服您的错误。
Dog
和 Cat
类是 Animal
抽象类的具体类,那么您可以简单地定义一个 doSomething(Animal animal)
并且该方法将起作用。doSomething
方法变得通用。例如
public <T> void doSomething(<T extends Animal> animal)