mchen
mchen

Reputation: 10136

How to use two near-identical classes that do not share the same parent class?

I have 2 near-identical classes that ideally should share the same parent class but don't (because they come from separate libraries whose source code I cannot change).

To illustrate in an example, I have two classes like:

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");
    }
}

Now ideally I want to do something like:

Animal[] animals = {new Cat(), new Dog()};
for (Animal animal : animals) {
    animal.speak();
    animal.eat(animal.findFood());
}

but Cat and Dog don't inherit from Animal and I can't change their source code. I would also like to avoid relying on instanceof as a crutch:

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
}

The code just gets duplicated a zillion times, so if I make a small change to the logic I have to copy and paste a zillion times also.

So how can I use these classes with minimal code duplication?

Upvotes: 1

Views: 62

Answers (2)

Georg Leber
Georg Leber

Reputation: 3605

You can use the Adapter pattern, but you need to implement a concrete Adapter for every type of animal:

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());
    }
}

And using a factory might encapsulate the concrete creation:

class AnimalFactory {
    public static Animal createAdapter(Dog dog) {
        return new DogAdapter(dog);
    }

    public static Animal createAdapter(Cat cat) {
        return new CatAdapter(cat);
    }
}

Then you can use the Adapter and run in a loop:

Animal[] animals = {AnimalFactory.createAdapter(cat), AnimalFactory.createAdapter(dog)};

for (Animal animal : animals) {
    animal.speak();
    animal.eat();
} 

One painpoint is the method eat() because the DogFood, CatFood, ... also has no common super type.

Upvotes: 3

homik
homik

Reputation: 563

If you can't modify these classes and you want to keep the code at its minimum you can use reflection to call these methods. Something like:

Method method = obj.getClass().getMethod("speak");
method.invoke(obj);

But consider using reflection as a last resort only

Upvotes: 0

Related Questions