Java – 从接口types而不是类声明
为了正确掌握界面的最佳实践,我注意到如下的声明:
List<String> myList = new ArrayList<String>();
代替
ArrayList<String> myList = new ArrayList<String>();
– 我的理解原因是因为它允许灵活性的情况下有一天,你不想实现一个ArrayList,但也许是另一种types的列表。
有了这个逻辑,我设立了一个例子:
public class InterfaceTest { public static void main(String[] args) { PetInterface p = new Cat(); p.talk(); } } interface PetInterface { public void talk(); } class Dog implements PetInterface { @Override public void talk() { System.out.println("Bark!"); } } class Cat implements PetInterface { @Override public void talk() { System.out.println("Meow!"); } public void batheSelf() { System.out.println("Cat bathing"); } }
我的问题是,我不能访问batheSelf()方法,因为它只存在于Cat。 这使我相信,我只应该从接口声明,如果我只打算使用在接口中声明的方法(而不是从子类中的额外方法),否则我应该直接从类声明(在这种情况下,猫)。 我是否正确的这个假设?
当通过interface
或class
引用一个对象的时候有一个select,前者应该是首选的,但是只有在存在适当的types的情况下 。
考虑String
作为一个例子implements
CharSequence
。 你不应该盲目地使用CharSequence
来select所有的String
,因为这会拒绝你简单的操作,比如trim()
, toUpperCase()
等等
但是,仅仅使用String
来关心其char
序列的方法应该使用CharSequence
,因为在这种情况下,这是适当的types。 这实际上是在String
类中replace(CharSequence target, CharSequence replacement)
的情况。
另一个例子是java.util.regex.Pattern
和它的Matcher matcher(CharSequence)
方法。 这使得Matcher
可以从Pattern
中创build,不仅仅是String
,还可以创build其他所有的CharSequence
。
一个很好的例子,在一个interface
应该被使用的地方,但不幸的是,它也可以在Matcher
find:它的appendReplacement
和appendTail
方法只接受StringBuffer
。 自从1.5版以来,这个类已经在很大程度上被更快的表兄StringBuilder
所取代。
一个StringBuilder
不是一个StringBuffer
,所以我们不能在Matcher
使用append…
方法。 但是,它们都implements
Appendable
(也在1.5中介绍过)。 理想的Matcher
的append…
方法应该接受任何Appendable
,然后我们可以使用StringBuilder
,以及所有其他的Appendable
!
所以我们可以看到, 当一个合适的types存在时 ,通过它们的接口引用对象可能是一个强大的抽象,但只有当这些types存在的时候。 如果types不存在,那么你可以考虑定义一个你自己的,如果它是有道理的。 在这个Cat
例子中,例如,你可以定义interface SelfBathable
。 然后,而不是提到一个Cat
,你可以接受任何SelfBathable
对象(例如一个Parakeet
)
如果创build一个新types是没有意义的,那么通过一切手段你可以通过它的class
来引用它。
也可以看看
- 有效的Java第二版,第52项:通过接口引用对象
如果存在适当的接口types,那么参数,返回值和字段都应该使用接口types来声明。 如果你养成使用接口types的习惯,你的程序将会更加灵活。 如果没有合适的接口存在,则通过类引用一个对象是完全合适的。
相关链接
- 错误ID:5066679 –
java.util.regex.Matcher
应该更多地使用Appendable
是的,你是对的。 您应该声明为最常用的types,提供您使用的方法。
这是多态的概念。
你是正确的,但是如果你需要,你可以从界面转换到所需的宠物。 例如:
PetInterface p = new Cat(); ((Cat)p).batheSelf();
当然,如果你试图把你的宠物扔给狗,你不能调用batheSelf()方法。 它甚至不会编译。 所以,为了避免出现问题,可以使用这样的方法:
public void bathe(PetInterface p){ if (p instanceof Cat) { Cat c = (Cat) p; c.batheSelf(); } }
使用instanceof
,确保在运行时不会让狗洗澡。 这会抛出一个错误。
是的,你是对的。 通过让Cat实现“PetInterface”,您可以在上面的示例中使用它,并轻松添加更多种类的宠物。 如果你真的需要成为特定的猫,你需要访问Cat类。
你可以在Cat中打电话给方法batheSelf
。
一般来说,你应该更喜欢接口到具体的类。 沿着这些路线,如果你可以避免使用new操作符(它总是需要一个具体的types,就像在你的新的ArrayList例子中那样),甚至更好。
这一切都与pipe理代码中的依赖关系有关。 最好只依靠高度抽象的东西(比如界面),因为它们也往往非常稳定(见resources/articles/stability.html )。 因为他们没有代码,所以只有当API改变时才需要更改…换句话说,当你希望这个接口向世界呈现不同的行为,即devise改变。
另一方面,课程总是在变化。 只要API的input和输出不会改变,调用者就不必关心,而依赖于类的代码并不关心它如何做。
你应该努力根据开放原则(见resources/articles/ocp.html )来确定你的类的行为,即使你添加了function,现有的接口也不需要改变,你可以指定一个新的子接口。
避免新运营商的旧方法是使用抽象工厂模式,但是它带有一系列问题。 更好的办法是使用像Guice这样的工具来进行dependency injection,并且更喜欢构造器注入。 在开始使用dependency injection之前,请确保您了解依赖倒置原则(请参阅resources/articles/dip.html )。 我看到很多人注入了不适当的依赖关系,后来又抱怨说这个工具没有帮助他们……它不会让你成为一个优秀的程序员,你仍然需要适当地使用它。
例如:你正在写一个帮助学生学习物理的程序。 在这个项目中,学生们可以在各种不同的场景中放置一个球,并观察它的performance:从悬崖上的大炮中射出来,放在水下,在深空等地方。问题:你想包括一些沉重的东西Ball API中的球…应该包含getMass()方法还是getWeight()方法?
重量取决于球所在的环境。调用者可以方便地调用一种方法,并在任何地方发挥球的重量,但是如何编写这种方法呢? 每个球的实例必须不断追踪它在哪里以及当前引力常数是什么。 所以你应该更喜欢getMass(),因为质量是球的内在属性,并不依赖于它的环境。
等等,如果你只是使用getWeight(Environment)呢? 通过这种方式,ball实例可以将当前g从环境中移出并继续…更好的是,您可以使用Guice在Ball的构造函数中注入Environment! 这是我经常看到的误用types,最终人们指责Guice不能像他们所希望的那样无缝处理dependency injection。
这个问题不是Guice,它是Ball API的devise。 重量不是球的固有属性,所以它不是一个应该从球中获得的属性。 相反,Ball应该使用getMass()方法来实现MassiveObject接口,Environment应该有一个名为getWeightOf(MassiveObject)的方法。 环境本身就是它自己的引力常数,所以这个好多了。 和环境只依赖于一个简单的接口,MassiveObject …但它的工作是包含对象,所以这是应该的。
为什么不简单地做这个!
Cat c = new Cat(); PetInterface p = (PetInterface)c; p.talk(); c.batheSelf();
现在我们有一个对象,可以使用2个引用进行操作。
引用p可以用来调用在接口中定义的函数,而c可以用来调用在类(或超类)中定义的函数。