Как использовать два почти идентичных класса, которые не разделяют один и тот же родительский класс?
У меня есть 2 почти идентичных класса, которые в идеале должны иметь один и тот же родительский класс, но не имеют (потому что они приходят из отдельных библиотек, исходный код которых я не могу изменить).
Чтобы проиллюстрировать на примере, у меня есть два класса, как:
public class Cat {
public void speak() {
System.out.println("Meow");
}
public CatFood findFood() {
return new CatFood();
}
public void eat(CatFood food) {
System.out.println("[Cat] Yum yum");
}
}
public class Dog {
public void speak() {
System.out.println("Woof");
}
public DogFood findFood() {
return new DogFood();
}
public void eat(DogFood food) {
System.out.println("[Dog] Yum yum");
}
}
Теперь в идеале я хочу сделать что-то вроде:
Animal[] animals = {new Cat(), new Dog()};
for (Animal animal : animals) {
animal.speak();
animal.eat(animal.findFood());
}
но Cat
а также Dog
не наследуй от Animal
и я не могу изменить их исходный код. Я также хотел бы не полагаться на instanceof
как костыль:
for (Object animal : animals) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.speak();
dog.eat(dog.findFood());
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.speak();
cat.eat(cat.findFood());
} else if (animal instanceof Rabbit) { // etc etc
}
Код просто дублируется миллион раз, поэтому, если я внесу небольшое изменение в логику, мне придется скопировать и вставить миллион раз.
Итак, как я могу использовать эти классы с минимальным дублированием кода?
2 ответа
Вы можете использовать шаблон Адаптера, но вам нужно реализовать конкретный Адаптер для каждого типа животных:
interface Animal {
void speak();
void eat();
}
class DogAdapter implements Animal {
private Dog dog;
public DogAdapter(Dog dog) {
this.dog = dog;
}
public void speak() {
dog.speak();
}
public void eat() {
dog.eat(dog.findFood());
}
}
class CatAdapter implements Animal {
private Cat cat;
public CatAdapter(Cat cat) {
this.cat = cat;
}
public void speak() {
cat.speak();
}
public void eat() {
cat.eat(cat.findFood());
}
}
И использование фабрики может инкапсулировать конкретное творение:
class AnimalFactory {
public static Animal createAdapter(Dog dog) {
return new DogAdapter(dog);
}
public static Animal createAdapter(Cat cat) {
return new CatAdapter(cat);
}
}
Затем вы можете использовать адаптер и запустить в цикле:
Animal[] animals = {AnimalFactory.createAdapter(cat), AnimalFactory.createAdapter(dog)};
for (Animal animal : animals) {
animal.speak();
animal.eat();
}
Одна болевая точка - метод eat()
потому что DogFood, CatFood, ... также не имеют общего супер типа.
Если вы не можете изменить эти классы и хотите сохранить код на минимуме, вы можете использовать рефлексию для вызова этих методов. Что-то вроде:
Method method = obj.getClass().getMethod("speak");
method.invoke(obj);
Но рассмотрите возможность использования отражения только в крайнем случае