【问题标题】:interface and contract : in this example接口和合约:在这个例子中
【发布时间】:2012-01-10 13:18:21
【问题描述】:

所以我试图理解界面,但我几乎只看到解释“如何”使用界面的文章,我的问题是理解“为什么”:
所以最好使用 Interface,而不是创建和子类化一个类,这可能没用,
所以我们在类中实现了接口方法,但是我不明白为什么这是一件好事,

比方说:
Car.java 这样的类定义了制造汽车的所有代码
我们使用 start()、stop() 等多种方法创建接口 Working.java
我们实现了Diesel_Car.javaElectric_Car.java等中的方法
那么Car.java 有什么变化呢?这可能不是最好的例子,因为 Car 似乎应该是 Diesel_Car.java 等的父级,
但是在这些类中实现方法的意义是什么?
Car.java 中是否有方法以某种方式“调用”Diesel_Car.java 类及其接口方法?

我读过这个接口就像一个“合同”,但我只看到了这个合同的第二部分(方法实现的地方),我很难想象第一部分发生在哪里?

感谢您的帮助

【问题讨论】:

  • Class vs. Interface 的可能重复项
  • “第一部分”是合约的定义,第二部分是实现。这是一件“好事”,因为它意味着功能是独立于实现的(我可以将每个实现都视为 Car),并且一个类可以实现多个接口。
  • 这个问题以前有人问过,见stackoverflow.com/questions/416331/java-interfaces
  • @Dave Newton :谢谢戴夫,我还是有点困惑,即使我已经阅读了所有使用接口的“规则”......也许你有一个例子可以让一切更清楚?
  • @Paul 添加(高度)人为的示例作为格式化目的的答案。

标签: java interface


【解决方案1】:

让我们以 Car 的基类和 Electric_CarDiesel_Car 子类为例,并稍微扩展一下模型。

汽车可能有以下接口

  1. Working :使用 start()stop() 方法
  2. Moving :使用 move()turn()stop() 方法

Car 可能包含类AirConditioner 的实例,该实例还应实现接口Working

Driver 对象可以与对象交互而不是实现 Working ,驱动程序可以 start()stop() 。 (司机可以单独启动或停止汽车和空调)。

另外,由于Driver 可以自己走动(并不总是需要汽车),他应该实现接口MovingGround 对象现在可以与任何实现 Moving 的对象交互:汽车或司机。

【讨论】:

  • 感谢 Nitzan,我想我开始了解更多了,但我还是有点迷茫。你有另一个例子来描述接口吗?我真的很感激
  • 我会试着想另一个例子,仍然试图准确地理解你遇到的困难。在上面的例子中,你还缺少什么?
  • 我想知道 Ground 需要在哪种情况下处理 Car,然后将 Car 的类型更改为 Driver。更多的是使用界面的“为什么”。
  • 也许 Ground 不是这里的最佳选择,但是为了示例的缘故,假设 Ground 通过调用 move() turn() & stop() 来控制其上的每个对象的移动地面可能需要持有一个 List allObjects;因此它可以遍历所有对象以移动它们。为了使对象顶部位于此地面上,该对象必须实现移动接口
  • 好的,所以我认为有些东西可以帮助我理解它:如果我们将来对我们的类(汽车、司机等)进行更改,接口是更新代码的好方法:假设我们有一个Driver,不管有没有许可证,Driver 将在像World 这样的类中实现(仅用于示例)--> 所以不要多次更改代码、层次结构和所有使用Driver 的类,我们可以创建Driver 接口,并实现Driver driver1 = new DriverWithLicence()driver1 = new DriverWithoutLicence(),就是这样!
【解决方案2】:

(非常)人为的示例(为了清楚起见,非泛型、删除了错误处理等)。

List theList = new ArrayList();

theListList,在这种情况下由 ArrayList 实现。假设我们将其传递给第三方 API,该 API 在其内部某处向列表中添加了一些内容。

public void frobList(List list) {
    list.add(new Whatever());
}

现在假设出于某种原因我们想要对添加到列表中的项目做一些不寻常的事情。我们无法更改第三方 API。但是,我们可以创建一个新的列表类型。

public FrobbableList extends ArrayList {
    public boolean add(E e) {
        super.add(Frobnicator.frob(e));
    }
}

现在在我们的代码中,我们更改了我们实例化的列表并像以前一样调用 API:

List theList = new FrobbableList();
frobber.frobList(theList);

如果第三方 API 采用 ArrayList(实际类型)而不是 List(接口),我们将无法轻松做到这一点。通过不将 API 锁定到特定的实现,它为我们提供了创建自定义行为的机会。

进一步说,这是可扩展、可调试、可测试代码的基本概念。 dependency injection/Inversion of Control 之类的东西依赖于对接口的编码来发挥作用。

【讨论】:

  • 谢谢戴夫,我想我终于明白你的例子了,谢谢你的回答;-)
【解决方案3】:

我正在尝试将接口的概念解释为合同。

一个典型的使用场景是当您想使用 java.util.Collections 对元素列表进行排序时:<T extends java.lang.Comparable<? super T>> void sort(java.util.List<T> ts)

这个签名是什么意思? sort() 方法将接受 T 类型对象的 java.util.List<T>,其中 T 是实现接口 Comparable 的对象。

所以,如果您想将 Collections.sort() 与您的对象列表一起使用,您将需要它们来实现 Comparable 接口:

public interface Comparable<T>   {   
    int compareTo(T t);
}

因此,如果您实现 Car 类型的类并希望使用 Collections.sort() 按重量比较汽车,则必须在 car 类中实现 Comparable 接口/合同。

public class Car implements Comparable<Car> {
   private int weight;

   //..other class implementation stuff

   @Override
   public int compareTo(Car otherCar) {
       if (this.weight == otherCar.weight) return 0;
       else if (this.weight > otherCar.weight) return 1;
       else return -1;
   }
} 

在对列表进行排序时,Collections.sort() 将调用您的 compareTo 实现。

【讨论】:

  • 好的,所以这个方法就像一个回调,处理这个回调的代码已经写在别的地方了……好吧,我想我明白了,非常感谢你的回答!
【解决方案4】:

契约是类如何相互协作的概念。这个想法是接口类定义方法返回类型和名称,但不提供如何实现的想法。这是由实现类完成的。

这个概念是,当接口 A 定义方法 A 和 B 时,任何实现该接口的类都必须实现 A 和 B 以及它自己的方法。所以它可能会像这样工作:

interface InterfaceA {
    void methodA();
    void methodB(String s);
}

public class ClassA implements InterfaceA {
     public void methodA() { 
         System.out.println("MethodA");
     }

     public void methodB(String s) {
         System.out.println(s);
     }
}

契约原则是任何实现接口的东西都必须实现整个接口。任何不这样做的东西都必须是抽象的。

希望这会有所帮助。

【讨论】:

  • 感谢 MikeyB,这是我们使用接口应该做的“什么”,但不是事情是如何工作的,当方法在类中实现时接下来会发生什么......
  • @Paul 你是什么意思,“接下来会发生什么”?您是在问 Java 方法分派是如何工作的吗?
  • @Dave Newton :好吧,看来重点是能够改变我们正在处理的对象的类型(通过将接口指定为对象的类型),但是你为什么要这样做?
  • @Paul 因为我在示例中提供的原因,用于 DI/IoC 等。此外,这不仅仅是改变实现,而是能够将异构集合视为相似类型。我继承是这样做的一种方式,但在 Java 中,它仅限于单个层次结构,这并不总是有用的。 (通常不会。)
【解决方案5】:

按合同设计 (DbC),也称为按合同编程和按合同设计编程,是一种设计计算机软件的方法。它规定软件设计者应该为软件组件定义正式的、精确的和可验证的接口规范,这些规范扩展了抽象数据类型的普通定义,带有前置条件、后置条件和不变量。根据商业合同的条件和义务的概念隐喻,这些规范被称为“合同”。 Wikipedia

捷径。

如果您遵循针对接口进行编码的良好实践,您就会知道接口定义了所有实现类必须遵守的约定。

我们设计了 Contract Java,它是 Java 的扩展,其中方法合同在接口中指定。我们确定了三个设计目标。

  • 首先,合同没有合同的Java程序和有合同的程序 完全满足的合约应该表现得好像它们没有运行 Java 中的合约。
  • 其次,使用传统 Java 编译器编译的程序必须能够与程序互操作 在 Contract Java 下编译。
  • 最后,除非一个类声明它满足特定的约定,否则绝不能因为未能满足该约定而受到责备 合同。抽象地说,如果调用了一个类型为 t 的对象的方法 m,那么调用者应该只为 与 t 和 m 相关的前置条件合同只应归咎于相关的后置条件合同 与 t。 这些设计目标提出了几个有趣的问题,并要求做出平衡语言设计与 软件工程问题。本节描述了每个主要的设计问题、替代方案、我们的决定、 我们的理由,以及决定的后果。决策不是正交的;后来的一些决定 依赖于早期的。

Contract Java 中的合同是接口中方法签名的装饰。每个方法声明都可能来 带有前置条件表达式和后置条件表达式;两个表达式都必须计算为布尔值。这 前置条件指定调用方法时必须为真。如果失败,方法调用的上下文是 责备没有在适当的上下文中使用该方法。后置条件表达式指定什么时候必须为真 该方法返回。如果失败,则方法本身应归咎于没有建立承诺的条件。 契约 Java 不限制契约表达式。尽管如此,良好的编程纪律要求表达式 不应该对程序的结果做出贡献。特别是,表达式不应有任何副作用。 前置条件和后置条件表达式都通过方法和伪变量的参数进行参数化 这。后者绑定到当前对象。此外,合同的后置条件可以参考 方法名,绑定到方法调用的结果。 合同是根据方法调用的类型上下文强制执行的。如果对象的类型是接口类型,则 方法调用必须满足接口中的所有约定。例如,如果一个对象实现了接口 I,则调用 对于 I 的方法之一,必须检查 I 中指定的前置条件和后置条件。如果对象的类型是类 类型,该对象没有合同义务。由于程序员总是可以为任何类创建接口,我们 出于效率原因,不要选中具有类类型的对象。 例如,考虑接口 RootFloat:

interface RootFloat {
    float getValue ();
    float sqRoot ();
    @pre { this.getValue() >= 0f }
    @post { Math.abs(sqRoot * sqRoot - this.getValue()) < 0.01f }
}

它描述了提供 sqRoot 方法的浮点包装类的接口。第一种方法 getValue 没有 合同。它不接受任何参数并返回未包装的浮点数。 sqRoot 方法也不接受任何参数, 但有合同。前置条件断言展开的值大于或等于零。结果类型 sqRoot 是浮动的。后置条件规定结果的平方必须在浮点值的 0.01 范围内。 即使合约语言足够强大,可以在某些情况下指定完整的行为,例如 前面的例子,完全甚至部分正确不是我们设计这些合约的目标。通常,合同 不能表达方法的全部行为。事实上,在披露的信息量之间存在张力 接口和合约可以满足的验证量。 例如,考虑这个堆栈接口:

interface Stack {
    void push (int i);
    int pop ();
}

由于界面中只有 push 和 pop 操作可用,因此无法指定在 push 之后,顶部 堆栈中的元素是刚刚压入的元素。但是,如果我们用一个 top 操作来扩充接口, 显示堆栈中最顶部的项目(不删除它),然后我们可以指定 push 将项目添加到堆栈的顶部 堆栈:

interface Stack {
     void push (int x);
     @post { x = this.top() }
     int pop ();
     int top ();
}

总之,我们不限制合同的语言。这使得合约语言尽可能灵活; 合同表达式评估甚至可能有助于计算的最终结果。尽管具有灵活性 合同语言,并非所有合意的合同都是可表达的。有些合同是无法表达的,因为它们可能 涉及检查无法确定的属性,而其他属性则无法表达,因为接口不允许足够 观察。

【讨论】:

  • 不过,这与 Java 本身相去甚远。
猜你喜欢
  • 1970-01-01
  • 2021-02-11
  • 2013-01-08
  • 2021-04-02
  • 2022-07-06
  • 2022-01-09
  • 2014-08-20
  • 2017-05-25
  • 1970-01-01
相关资源
最近更新 更多