Dus*_*yte 8 java design-patterns
习惯于老式的程序化C编程,我现在正在学习Java,并且可能已经明显地认识到硬件是设计而不是语法.
在想到如何填充我的动物园后,我花了几个小时的时间去了解想法:
我有一个class Zoo和一个抽象的class Animal.有几个非抽象的子类Animal叫Lion,Giraffe,Zebra,Penguin,等.甲Zoo对象将包含的每个子类中的恰好一个实例Animal,并且每个这样的实例包含于唯一的参考Zoo实例它属于.
我想以特定的顺序在动物园迭代动物(当你沿着一条小径行走时),以及在动物园里用字典查找动物.更确切地说,在某些时候,我将解析包含动物名称的文本文件(例如,对于字符串"LION",我想获得唯一的Lion实例).字符串与动物的一对一映射.我已经决定使用了LinkedHashMap<String, Animal>.
我想编写可管理的代码,以便将来轻松添加更多动物.到目前为止,我最好的方法如下.
在Zoo类中,我定义了一个enum反映我想要的动物的顺序,它的元素完全对应于我将在文本文件中解析的字符串.
private enum Species { LION, GIRAFFE, ZEBRA, PENGUIN };
Run Code Online (Sandbox Code Playgroud)
在Zoo我也有创建的动物对象的方法:
private Animal makeAnimal(Species species)
{
switch (species)
{
case LION:
// create a Lion object;
break;
case GIRAFFE:
// ...
}
// return the Animal object created above;
}
Run Code Online (Sandbox Code Playgroud)
作为Zoo我的构造函数的一部分迭代enum并将元素插入到被LinkedHashMap调用的animals:
for (Species species : Species.values())
animals.put(species.name(), makeAnimal(species));
Run Code Online (Sandbox Code Playgroud)
要添加一种新的动物,我必须这样做
Animal,enum,switch语句中添加一个案例makeAnimal(Species species).这是一个健全和理智的方法吗?现在花了很多时间把这个问题写下来之后,我实际上对我的方法很满意;),但也许我错过了一个明显的设计模式,我的解决方案将在某些时候回击.我觉得名称"LION"和它之间存在不合理的分离class Lion并不理想.
阅读你的问题我发现了Zoo的以下要求:
正如您所提到的,字符串"LION"和类之间存在不合需要的分离Lion.在Effective Java,Item 50中,以下是关于字符串的说明:
字符串是其他值类型的不良替代品.当一段数据从文件,网络或键盘输入进入程序时,它通常是字符串形式.有一种自然倾向,就是这样,但只有当数据本质上是文本性的时候,这种趋势才是合理的.如果它的数字,应该翻译成相应的数字类型,如
int,float或BigInteger.如果这是一个是或否的问题的答案,它应该被翻译成一个boolean.更一般地说,如果存在适当的值类型,无论是原始值还是对象引用,都应该使用它; 如果没有,你应该写一个.虽然这个建议似乎很明显,但它经常被违反.
因此,不应按物种名称查找动物,而应根据物种实例查找动物.
要定义物种的排序,您需要使用具有可预测迭代顺序的集合,例如上面提到的LinkedHashMap.
目前添加动物包括您提到的三个步骤.使用枚举的一个副作用是只有访问源代码的人才有能力添加新物种(因为Species枚举必须扩展).
现在考虑一下,Species.LION这就是Lion班级的种类.请注意,此关系在语义上与类及其实例化之间的关系相同.因此,更优雅的解决方案是Lion.class用作物种Lion.当您免费获得物种时,这也减少了添加动物的步骤数.
在你的建议中,动物园有责任创造动物.结果是每个动物园都需要使用所有定义的动物(因为Species枚举)并使用指定的顺序,动物园之间不会有变化.最好将动物的动物与动物园分开,以使动物和动物园更具灵活性.
由于它们的灵活性,接口应该优于抽象类.正如Effective Java,第18项中所述:
项目18.首选接口到抽象类
Java编程语言提供了两种机制来定义允许多个实现的类型:接口和抽象类.两种机制之间最明显的区别是允许抽象类包含某些方法的实现,而接口则不允许.更重要的区别是,要实现抽象类定义的类型,类必须是抽象类的子类.任何定义所有必需方法并遵守常规合同的类都允许实现接口,无论类位于类层次结构中的何处.因为Java只允许单继承,所以对抽象类的这种限制严重限制了它们作为类型定义的使用.
在你的问题中,你提到动物也应该参考它所在的动物园.这引入了一个循环引用,应该尽可能避免.
循环类引用创建高耦合; 每次更改其中任何一个类时,都必须重新编译这两个类.
public interface Animal {}
public class Zoo {
private final SetMultimap<Class<? extends Animal>, Animal> animals;
public Zoo() {
animals = LinkedHashMultimap.create();
}
public void addAnimal(Animal animal) {
animals.put(animal.getClass(), animal);
}
@SuppressWarnings("unchecked") // the cast is safe
public <T extends Animal> Set<T> getAnimals(Class<T> species) {
return (Set<T>) animals.get(species);
}
}
Run Code Online (Sandbox Code Playgroud)
static class Lion implements Animal {}
static class Zebra implements Animal {}
final Zoo zoo = new Zoo();
zoo.addAnimal(new Lion());
zoo.addAnimal(new Zebra());
zoo.addAnimal(new Lion());
zoo.getAnimals(Lion.class); // returns two lion instances
zoo.getSpeciesOrdering(); // returns [Lion.class, Zebra.class]
Run Code Online (Sandbox Code Playgroud)
上面的实现支持每个物种的多个动物实例,因为这似乎对动物园更有意义.如果只需要一只动物,可以考虑使用Guava的ClassToInstanceMap而不是SetMultimap.
动物的创造不被视为设计问题的一部分.如果需要构建更复杂的动物,请考虑使用构建器模式.
添加动物现在就像创建一个实现Animal界面并将其添加到动物园的新类一样简单.
小智 5
虽然可能是一件小事,但应该注意避免不必要的循环引用/依赖.我不想援引认为,所有形式的循环依赖是不好的教条式的点,但是这取决于你想如何使用你的程序,你可能没有动物更好地依靠动物园和动物园依赖多个动物同时出现.
在您的示例中,Zoo和Animal类可能比它们必须更紧密耦合.如果要将动物重新定位到另一个动物园,则必须同时更新动物园实例和动物实例.如果你想保持双向通航,可以使用例如,在动物园一个双向映射,作为动物园将有聚集动物的责任(这也是什么动物园确实在现实世界中,想起来吧),但动物园会再也是保持这种关系的唯一地方.在我看来,这更清洁.
关于循环引用的另一个讨论可以在这里找到:https: //softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references