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。 这使我相信,我只应该从接口声明,如果我只打算使用在接口中声明的方法(而不是从子类中的额外方法),否则我应该直接从类声明(在这种情况下,猫)。 我是否正确的这个假设?

当通过interfaceclass引用一个对象的时候有一个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应该被使用的地方,但不幸的是,它也可以在Matcherfind:它的appendReplacementappendTail方法只接受StringBuffer 。 自从1.5版以来,这个类已经在很大程度上被更快的表兄StringBuilder所取代。

一个StringBuilder不是一个StringBuffer ,所以我们不能在Matcher使用append…方法。 但是,它们都implements Appendable (也在1.5中介绍过)。 理想的Matcherappend…方法应该接受任何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可以用来调用在类(或超类)中定义的函数。