Home > database >  How can I structure my classes which usually need to be called together?
How can I structure my classes which usually need to be called together?

Time:02-20

I have some related classes that implement the same method

class Dog {
    public void speak() { System.out.println("Bark") }
}
class Cat {
    public void speak() { System.out.println("Meow") }
}

90% of the time, users would want both the dog and the cat to speak. They don't want to know the details. When we add a new animal, they'll want it to speak too. The idea is to avoid:

// All animals need to be explicitly told to speak every time
new Dog().speak();
new Cat().speak();

// But we just added Birds, and the users need to remember to add this call everywhere
new Bird.speak();

I could do something like

class Animals {
    public void speak() {
        new Dog().speak();
        new Cat().speak();
        new Bird().speak();
    }
}

So that users can just call new Animals().speak() every time.

However, 10% of the time, it does need to be configurable. What I want is a way for users to do something like this

// Used most of the time
Animals.withAllAnimals().speak();

// Sometimes they don't want cats, and they want the dogs to woof instead
Animals.exclude(Cat)
  .configure(Dog.sound, "Woof")
  .speak();

How can I structure my classes to accomplish this?

CodePudding user response:

I know that it is question tagged with java. However, let me show an example with C# as these languages have many common things. The first thing I would use inheritance and create abstract class Animal as common behaviour Speak() is used:

public abstract class Animal
{
    public abstract void Speak();
}

Then just use inheritance and override behaviour in derived classes:

public class Bird : Animal
{
    public override void Speak()
    {
        System.Console.WriteLine("I am a bird!");
    }
}

public class Cat : Animal
{
    public override void Speak()
    {
        System.Console.WriteLine("I am a cat!");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        System.Console.WriteLine("I am a dog!");
    }
}

Then we need a class that allows to speak for all animals. Let's create Choir class for this purpose:

public class Choir
{
    private List<Animal> animals;

    public Choir()
    {
        Init();
    }

    private void Init()
    {
        animals = new List<Animal>()
        {
            new Cat(),
            new Bird(),
            new Dog()
        };
    }

    public void SpeakAll()
    {
        foreach (Animal animal in animals)            
            animal.Speak();            
    }

    public void Speak(Func<Animal, bool> filter)
    {
        IEnumerable<Animal> filteredAnimals = animals.Where(filter ?? (animal => true));
        foreach (Animal animal in filteredAnimals)            
            animal.Speak();            
    }
}

Pay attention to Speak() method. It can take a predicate as a parameter, so you can take desired animals to speak().

CodePudding user response:

Here are some ideas.

import java.util.*;
import java.util.function.Consumer;

/**
 * An animal can either speak with its own voice, or another supplied
 */
interface Animal {
    String speak();
    String speak(String voice);
}

/**
 * Base class for animal implementations.
 * 
 * An animal is added to a Menagerie when created.
 */
abstract class BaseAnimal implements Animal {
    private final String defaultVoice;

    public BaseAnimal(Menagerie menagerie, String defaultVoice) {
        this.defaultVoice = defaultVoice;
        menagerie.add(this);
    }

    public String speak(String voice) {
        return voice;
    }

    public String speak() {
        return speak(defaultVoice);
    }
}

/**
 * A Dog. Even when given a voice the dog does things slightly differently.
 */
class Dog extends BaseAnimal {
    public Dog(Menagerie menagerie) {
        super(menagerie, "Bark!");
    }

    public String speak(String voice) {
        return voice   " (and drools)";
    }
}

/**
 * A collection of animals. We can do something side-effectful to each, or create a new collection where
 * some classes of animal are excluded or have different behaviour.
 */
interface Animals {
    void forEach(Consumer<Animal> action);
    Animals exclude(Class<Animal> clazz);
    Animals configureVoice(Class<Animal> clazz, String voice);
}

/**
 * An Animals instance which can contain only a single animal of each class
 * (an arbitrary decision based on the code in the question)
 */
class Menagerie implements Animals {
    Map<Class<? extends Animal>,Animal> animals = new HashMap<>();

    public Menagerie() {
    }

    public Menagerie(Map<Class<? extends Animal>, Animal> animals) {
        this.animals = new HashMap<>(animals);
    }

    public void add(Animal animal) {
        animals.put(animal.getClass(), animal);
    }

    public void forEach(Consumer<Animal> action) {
        animals.values().forEach(action);
    }

    @Override
    public Animals exclude(Class<Animal> clazz) {
        Menagerie m = new Menagerie(animals);
        m.animals.remove(clazz);
        return m;
    }

    /**
     * Return an Animals instance which contains a proxy for a particular type of animal
     * which will behave differently when speak() is called.
     */
    @Override
    public Animals configureVoice(Class<Animal> clazz, String voice) {
        Menagerie m = new Menagerie(animals);
        Animal a = m.animals.get(clazz);
        if (a != null) {
            m.animals.put(clazz, new Animal() {

                @Override
                public String speak() {
                    return voice;
                }

                @Override
                public String speak(String voice) {
                    return a.speak(voice);
                }
            });
        }
        return m;
    }
}

  • Related