如何构建通常需要一起调用的 类 ?

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

我有一些实现相同方法的相关 类

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

90% 的时间,用户希望狗和猫都能说话。他们不想知道细节。当我们添加一种新动物时,他们会希望它也能说话。这个想法是为了避免:

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

我可以做类似的事情

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

以便用户每次都可以调用 new Animals().speak()

但是,在 10% 的情况下,它确实需要可配置。我想要的是一种让用户做这样的事情的方法

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

我如何构造我的 类 来完成这个?

这里有一些想法。

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

我知道这是用 java 标记的问题。但是,让我举一个 C# 的例子,因为这些语言有很多共同点。 第一件事是我会使用继承并创建抽象 class Animal as common behaviour Speak() is used。因此,抽象 class 应该定义行为,派生的 classes 应该实现该行为:

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

然后在派生的 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!");
    }
}

那么我们需要一个class,让所有的动物都能说话。让我们为此创建 Choir class:

public class Choir
{
    private List<Animal> choristers;

    public void AddChoristers(IEnumerable<Animal> choristers)
    {
        if (choristers == null)
            choristers = new List<Animal>();

        choristers.AddRange(choristers);
    }

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

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

注意Speak()方法。它可以接受一个谓词作为参数,所以你可以选择想要的动物来 speak().