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