依赖项更改时,强制性类重新编译

时间:2018-12-01 22:54:57

标签: java c++ oop interface solid-principles

上下文:

我是Java程序员,正在阅读Bob叔叔敏捷软件开发。关于ISP接口隔离原则,我可以理解为:

让我们拥有:

interface Service {
  function doA();
}
class ServiceImpl implements Service {...}
class ServiceClient {
  // ServiceImpl is injected; eg either through constructor or setter
  private Service service;
  function useOnlyDoA() {
    service.doA();
  }
}

现在,如果接口服务发生更改,例如添加方法doB(),然后添加所有相关类,例如ServiceClient必须重新编译,即使它们不使用添加方法! (真的吗?)

  1. Java是否正确?
  2. 对于c ++是真的吗?
  3. 它适用于其他哪些语言,不是?

我坚信,如果ServiceClient位于软件包中,则关于Java。 client.jarService中的接口service.jarServiceImpl中的impl.jar的接口,则client.jar不必重新编译并重建(如果没有)请勿使用Service界面中的新方法,并且可以将其与service.jarimpl.jar的新版本一起使用。在我看来,java中的情况是这样的。 是其他语言,例如c ++还是其他语言?

可能在C ++中有更多损坏,例如 https://stackoverflow.com/a/4033900/1423685

为清楚起见:

我不是在问重新编译实现更改接口的类(这很明显,它必须实现新添加的方法)。 但是我问我是否必须重新编译使用此接口的ServiceClient类,即使该类未使用新添加的方法也是如此。可能是在c ++ BC更改中,确实必须重新编译客户端,但是在我看来,这不是在Java中。

编辑:

我用Java实现了一个测试。 4罐:

  • interface.jar-包含接口public interface Service
  • implementation.jar-包含public class ServiceImpl implements Service
  • 上面两个罐子变了
  • 即使下面的两个罐子都变了,两个下面的罐子也没变
  • interfaceclient.jar-依赖于interface.jar,包含将ClientOfService作为构造函数参数的类Service,并在其doA()方法中使用此服务
  • application.jar-取决于上述所有jar。主方法中的类App创建ServiceImpl实例,并将其作为arg传递给ClientOfService的构造函数,然后在ClientOfService上计算一个方法,该方法从Service: public static void main(String[] args) { Service service = new ServiceImpl(); ClientOfService clientOfService = new ClientOfService(service); System.out.println("App.main() :: calling clientOfService.doWorkCallingDoAFromService"); clientOfService.doWorkCallingDoAFromService(); System.out.println("App.main() :: end of main"); }调用方法doA()

更改interface.jar和Implementation.jar后,App中的主程序成功运行(我添加了一些未使用的方法,并删除了一个旧方法)。

因此面临的挑战是如何更改接口(当然,无需更改doA()方法声明)和实现以使其成功停止运行?可能吗?如果是这样,怎么办?

2 个答案:

答案 0 :(得分:4)

是的,如果更改了接口,则需要重新编译用Java和C ++实现的接口的类。这有几个原因,其中包括:

  • Java编译器需要检查实现接口的类是否仍然兼容(实现接口的类必须实现其所有方法)。
  • C ++编译器可能需要更改类的技术布局(例如vtable,如果doB()是虚拟的),并验证是否没有隐藏具有相同签名但在另一个基类中的另一个成员函数。新创建的一个。

ISP的全部目的就是避免这种情况。因此,如果实际上doB()与该接口无关,则最好使用仅包含doB()的隔离接口,并仅更改/重新编译需要实现该接口的类。

编辑:相同的原理适用于使用该接口的类。当然,其中一些参数可能取决于实现:

  • 对于C ++,为using类生成的代码将需要依赖正确的接口定义,以便可以对vtable布局做出正确的假设并生成正确的代码。这就是为什么使用类也需要重新编译的原因。
  • 对于Java,对接口方法的调用确实可以依赖于显式名称和运行时解析。我不是Java专家,所以这里有一个更正:实际上,对接口的某些更改是allowed by the Java Specifications,而无需重新编译。但是有些更改需要重新编译。

答案 1 :(得分:0)

  

对其他哪些语言来说这是真的,对哪些不是呢?

这实际上是特定于实现的特征,而不是语言特征。

语言实现通常需要重新编译,这些实现将接口上的成员访问编译到函数表的内存地址或数组索引。这就是大多数静态语言编译器的实现方式。大多数Java和C实现都属于此类。

对于编译成员对字典查找的访问的语言实现,则不需要重新编译(以便它使用函数名进行查找,而不是使用替代索引)。这就是大多数动态语言编译器的实现方式。例如,如果另一个文件中的类更改了其接口,则大多数Python实现无需重新创建.pyc文件。

因此,主要的区别是静态和动态语言之间。当然也有例外。可以使用某种静态语言来确保将任何新添加项添加到功能表中,因此使用旧索引的代码的现有代码仍然可以使用。对于大多数静态语言的库作者而言,重要的考虑因素是,如果语言实现允许,仅以保留二进制兼容性的方式更改接口。