Если уж совсем идеально, то:
interface Voice {
void say();
}
class Dog implements Voice { ... }
class Cat implements Voice { ... }
interface VoiceFactory {
Voice getVoice(String voiceType);
}
class VoiceFactoryImpl implements VoiceFactory {
private Map<String, Supplier<Voice>> suppliers = new HasMap<>();
void addSupplier(String type, Supplier<Voice> supplier) { suppliers.put(type, supplier); }
@Override
Voice getVoice(String type) {
final Supplier<Voice> supplier = suppliers.get(type);
if (supplier != null) {
return supplier.get();
} else {
throw new RuntimeException("No supplier for type: "+type);
}
}
}
Класс VoiceFactoryImpl выполняет принцип OCP - открыт для дополнений через метод addSupplier, в него можно добавлять новые сопоставления строка - животное и закрыт для изменений.