Метод перегрузки с подклассами в качестве параметров, но метод называется суперклассом

У меня есть абстрактный класс 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 в качестве параметров, но все же сможете вызывать их из 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(Animal)

Я знаю, что мог бы использовать что-то вроде instanceOf или же a.getClass().equals(type)Но я читал, что это плохая практика. Этот вопрос немного отличается от этого другого вопроса, так как я хочу два отдельных метода, каждый из которых имеет свой параметр.

РЕДАКТИРОВАТЬ: я собираюсь избегать использования шаблона посетителя, так как я не думаю, что он действительно хорошо вписывается в мою фактическую реализацию. Переместит метод doSomething() в каждый класс и проведет рефакторинг, чтобы обеспечить логический смысл (каждый класс выполняет действия, за которые он должен отвечать). Благодарю.

5 ответов

Решение

Да, instanceof и getclass сделают ваш класс зависимым от определенных типов, и использование таких методов не слишком хорошо.

Но имея методы, принимающие Dog или же Cat Вы снова окажетесь в зависимости от конкретной конкретной реализации.

Лучший способ определить метод в Animal интерфейс performAction(), Обеспечить реализацию в обоих Dog а также Cat, Теперь итерируйте свой Список животных и просто вызовите performAction() и полиморфно в соответствии с фактическим экземпляром будет вызван метод, реализованный в Dog или Cat. Позже, даже если в код добавлена ​​другая реализация Animal, вам не нужно изменять ваш код.

Таким образом, вы будете иметь связанную с Dog логику в классе Dog, логику, связанную с Cat в классе Cat, а не в каком-либо внешнем классе. Это поможет в соблюдении OCP и SRP (принципы открытой закрытой и единой ответственности).

Также, если вы хотите выполнить поведение позже, и ваш случай таков, что вы знаете, что ваши реализации исправлены, и хотите, чтобы использующий код вводил поведение позже, тогда я бы порекомендовал Visitor шаблон, но все же я чувствую, что этим не следует злоупотреблять (шаблон проектирования часто не используется, но злоупотребляется) иногда мы принудительно используем шаблон, когда он не нужен. Шаблон посетителя использует полиморфизм для отправки первого вызова в конкретный экземпляр, откуда вторая диспетчеризация вызывает перегруженные методы в классе, реализующем интерфейс Visitor. (имя может быть другим, я упомянул посетителя, чтобы подразумевать класс, реализующий концепцию посетителя). Используйте шаблон Visitor, например, реализацию, только если это необходимо, и обычный подход на основе полиморфизма не подходит к ситуации

Вместо того, чтобы выполнять итерации в своем клиентском методе и вызывать 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(){
      ArrayList<Animals> animals = getTheAnimals();
      for (Animal a : animals){
         a.doSomething();
      }
   }

Вам нужна двойная диспетчеризация: тип времени выполнения, предоставляемый 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 подклассы while Они должны иметь только способ вызова метода посетителя, а не любые открытые методы.
Таким образом, улучшение было бы ввести Visitor интерфейс для ограничения 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. Если твой Dog а также Cat классы - это конкретные классы для Animal абстрактный класс, тогда вы можете просто определить один doSomething(Animal animal) и метод будет работать.
  2. Вы можете попробовать сделать свой doSomethingметод универсальный. Такие как public <T> void doSomething(<T extends Animal> animal)
  3. Или вы можете определить абстрактный метод doSomething в Animal и сделать так, чтобы подклассы предоставляли для него свои собственные реализации, как было предложено Эндрю С. в приведенном выше ответе.
Другие вопросы по тегам