Как использовать два почти идентичных класса, которые не разделяют один и тот же родительский класс?

У меня есть 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);

Но рассмотрите возможность использования отражения только в крайнем случае

Другие вопросы по тегам