我有一个抽象类Animal
,它有两个扩展类Dog
和Cat
。
public abstract class Animal {
...
}
public class Dog extends Animal {
...
}
public class Cat extends Animal {
...
}
在另一个类中,我有一个ArrayList<Animal> animals
,它包含Cat
和Dog
的实例。
在使用ArrayList<Animal>
的类中,我希望能够重载以Dog d
或Cat 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(动物)的方法
我知道我可以使用类似instanceOf
或a.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会使您的类依赖于特定的类型,使用这样的做法并不太好。
但是,通过使用接受Dog
或Cat
的方法,您将再次依赖于具体的具体实现。
更好的方法是在Animal
接口performAction()
中定义方法。在Dog
和Cat
中提供实现。现在迭代您的动物列表,只需调用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中有一条重要规则定义了继承、接口和抽象类的大部分工作方式-超类引用变量可以容纳子类对象。因此,有几种方法可以克服您的错误。
- 如果您的
Dog
和Cat
类是Animal
抽象类的具体类,那么您只需定义一个doSomething(Animal animal)
,该方法就可以工作了 - 您可以尝试使
doSomething
方法通用。例如public <T> void doSomething(<T extends Animal> animal)
- 或者,您可以在Animal中定义一个doSomething抽象方法,并让子类为其提供自己的实现,正如Andrew S.在上面的回答中所建议的那样