Java - 从Interface类型而不是Class声明

时间:2010-08-01 20:53:35

标签: java interface

为了正确掌握界面最佳实践,我注意到了以下声明:

List<String> myList = new ArrayList<String>();

而不是

ArrayList<String> myList = new ArrayList<String>();

- 我理解的原因是因为它允许灵活性,有一天你不想实现ArrayList但可能是另一种类型的列表。

通过这个逻辑,我建立了一个例子:

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。这让我相信我只应该从接口声明我是否只会使用接口中声明的方法(而不是子类中的额外方法),否则我应该直接从类声明(在本例中为Cat)。我在这个假设中是否正确?

7 个答案:

答案 0 :(得分:33)

如果在通过interfaceclass引用对象之间做出选择,则前者应该是首选,但只有在存在适当的类型时。< / p>

String implements CharSequence为例。对于所有情况,您不应该盲目地使用CharSequence来优先String,因为这会拒绝您执行trim()toUpperCase()等简单操作。

但是,仅String关注其char序列的方法应使用CharSequence代替,因为这是合适的类型在这种情况下。事实上Stringreplace(CharSequence target, CharSequence replacement)就是这种情况。

另一个例子是java.util.regex.Pattern及其Matcher matcher(CharSequence)方法。这样,Matcher就可以Pattern创建String,而不仅仅是CharSequence,还可以为所有其他interface创建StringBuilder

MatcherappendReplacementappendTail和{{3}中也可以找到StringBuffer应该使用append…的一个很好的例子,但不幸的是,Matcher方法仅接受StringBuffer。自1.5以来,这个课程已经被其快速的堂兄StringBuilder所取代。

implements不是Matcher,因此我们不能在append…中使用Appendable方法使用前者。但是,他们都StringBuilder Appendable(也在1.5中引入)。理想情况下,Appendable的{​​{1}}方法应该接受任何Cat,然后我们就可以使用interface SelfBathable以及所有其他可用的Cat!< / p>

因此,我们可以看到当适当的类型存在时通过它们的接口引用对象可以是一个强大的抽象,但只有当这些类型存在时。如果类型不存在,那么如果有意义,您可以考虑定义自己的类型。在此SelfBathable示例中,您可以定义Parakeet。然后,您可以接受任何class对象(例如{{1}})

,而不是引用{{1}}。

如果创建新类型没有意义,那么您可以通过{{1}}来引用它。

另见

  • Effective Java 2nd Edition,Item 52:通过接口引用对象
      

    如果存在适当的接口类型,则应使用接口类型声明参数,返回值和字段。如果您养成使用界面类型的习惯,您的程序将更加灵活。如果没有合适的接口,则通过类引用对象是完全合适的。

相关链接

答案 1 :(得分:11)

是的,你是对的。您应该声明为提供您使用的方法的最常规类型。

这是多态的概念。

答案 2 :(得分:4)

您的信息是正确的,但如果需要,您可以从界面投射到所需的宠物。例如:

PetInterface p = new Cat();
((Cat)p).batheSelf();

当然,如果您尝试将宠物投射到狗身上,则无法调用batheSelf()方法。它甚至不会编译。因此,为避免出现问题,您可以采用以下方法:

public void bathe(PetInterface p){
    if (p instanceof Cat) {
        Cat c = (Cat) p;
        c.batheSelf();
    }
}

使用instanceof时,请确保在运行时不要让狗自己洗澡。这会引发错误。

答案 3 :(得分:2)

是的,你是对的。通过让Cat实现“PetInterface”,你可以在上面的例子中使用它,并轻松添加更多种类的宠物。如果您确实需要特定于Cat,则需要访问Cat类。

答案 4 :(得分:2)

您可以从Cat。中的batheSelf调用方法talk

答案 5 :(得分:2)

通常,您应该更喜欢具体类的接口。沿着这些方向,如果你可以避免使用new运算符(它总是需要像你的新ArrayList示例中的具体类型),那就更好了。

这一切都与管理代码中的依赖关系有关。最好只依赖于高度抽象的东西(比如接口),因为它们也往往非常稳定(见http://objectmentor.com/resources/articles/stability.pdf)。因为它们没有代码,所以只有在API更改时才需要更改它们...换句话说,当您希望该界面向世界呈现不同的行为时,即设计更改。

另一方面,课程一直在变化。依赖于类的代码并不关心它是如何做的,只要API的输入和输出没有改变,调用者就不应该关心。

你应该努力根据开放 - 封闭原则确定类的行为(参见http://objectmentor.com/resources/articles/ocp.pdf),即使添加功能,现有接口也不需要改变,你可以只指定一个新的子接口

避免使用新运算符的旧方法是使用抽象工厂模式,但它带有一系列问题。更好的是使用像Guice这样的工具来执行依赖注入,并且更喜欢构造函数注入。在开始使用依赖注入之前,请确保您了解依赖性倒置原则(请参阅http://objectmentor.com/resources/articles/dip.pdf)。我已经看到很多人注入了不适当的依赖关系然后抱怨该工具没有帮助他们......它不会让你成为一个优秀的程序员,你仍然必须适当地使用它。

示例:您正在编写一个帮助学生学习物理的程序。在这个项目中,学生可以将球放在各种物理场景中并观察它的行为:从悬崖上的大炮中射出,将它放在水下,在深空中等等。问题:你想要包括一些关于沉重的东西Ball API中的球......你应该包含一个getMass()方法还是一个getWeight()方法?

重量取决于球碰巧所处的环境。调用者可以方便地调用一种方法并在任何地方获得球的重量,但是你如何编写这种方法呢?每个球实例必须不断跟踪它的位置以及当前的重力常数。所以你应该更喜欢getMass(),因为质量是球的固有属性而不依赖于它的环境。

等等,如果你只是使用getWeight(环境)呢?通过这种方式,ball实例可以将其当前的g从环境中移除并继续......更好的是,您可以使用Guice在Ball的构造函数中注入环境!这是我经常看到的滥用类型,人们最终指责Guice无法像他们希望的那样无缝地处理依赖注入。

这里的问题不是Guice,而是Ball API设计。重量不是球的固有特性,所以它不是一个应该可以从球上接近的属性。相反,Ball应该使用getMass()方法实现MassiveObject接口,而Environment应该有一个名为getWeightOf(MassiveObject)的方法。环境的内在性是它自己的引力常数,所以这要好得多。而环境现在只依赖于一个简单的界面,MassiveObject ......但它的工作是包含对象,所以这是应该的。

答案 6 :(得分:0)

为什么不简单地做到这一点!

Cat c = new Cat();
PetInterface p = (PetInterface)c;
p.talk();
c.batheSelf();

现在我们有一个对象,可以使用2个引用进行操作 引用p可用于调用接口中定义的函数,c可用于仅调用类(或超类)中定义的函数。