Java 为什么要隐藏类实现?

Java 为什么要隐藏类实现?,java,oop,encapsulation,Java,Oop,Encapsulation,我被这个概念迷住了 这是我在一个网站上看到的解释的一部分: 隐藏实现 面向对象设计中的一个主要考虑因素是将变化的事物与保持不变的事物分开。 这对图书馆尤其重要。该库的用户(客户机程序员)必须能够依赖于他们使用的部分,并且知道如果库的新版本出现,他们将不需要重写代码。另一方面,库创建者必须有修改和改进的自由,确保客户端代码不会受到这些更改的影响。 这可以通过公约来实现。例如,库程序员必须同意在修改库中的类时不删除现有方法,因为这样会破坏客户端程序员的代码。然而,相反的情况更为棘手。对于字段,库创建

我被这个概念迷住了

这是我在一个网站上看到的解释的一部分:

隐藏实现

面向对象设计中的一个主要考虑因素是将变化的事物与保持不变的事物分开。 这对图书馆尤其重要。该库的用户(客户机程序员)必须能够依赖于他们使用的部分,并且知道如果库的新版本出现,他们将不需要重写代码。另一方面,库创建者必须有修改和改进的自由,确保客户端代码不会受到这些更改的影响。 这可以通过公约来实现。例如,库程序员必须同意在修改库中的类时不删除现有方法,因为这样会破坏客户端程序员的代码。然而,相反的情况更为棘手。对于字段,库创建者如何知道客户端程序员访问了哪些字段?对于仅是类实现的一部分,而不是客户端程序员直接使用的方法,也是如此。但是,如果库创建者想要删除旧的实现并放入新的实现,该怎么办?更改这些成员中的任何一个都可能会破坏客户端程序员的代码。因此,图书馆的创造者是在一个海峡夹克,不能改变任何事情

但我无法想象这种情况会发生在什么真实情况下

有人能给我举一个现实生活中的实际例子吗

这就是我的想象:

public void print(String message)
{
     System.out.println(message);
}

如果库客户端知道或不知道这个实现,会有什么区别?

一个简单的例子是,如果使用
log
方法记录错误。也许它所做的只是打印到控制台上。之后,您希望将错误写入文件:

public void log(String message)
{
     // write to file
}
然后,您决定写入数据库或进行远程调用:

public void log(String message)
{
     // make network call
}
只要实现是隐藏的,客户端就可以继续调用
log()
,并且不会因为您没有更改方法签名而中断任何操作


您还可以通过提取一个接口来实现此方法的多个实现,如@tima所示。

该实现并没有真正隐藏在视图中。尤其是因为您将使用的大多数库都是开源的。在本例中,“隐藏”是指不属于公共API的任何内容。库的公共API由库公开的公共方法和字段组成。一旦库发布了一个版本,它应该在未来的版本上继续支持公共API。其他任何东西都被认为是隐藏的,该库的用户不能依赖该库的未来版本中存在的“隐藏代码”

编辑:

但是,例如,如果代码没有隐藏,客户机如何依赖代码呢

假设一个库产生了一个如下所示的方法

public void doSomething(CharSequence x);
这被认为不是隐藏的,因为它是公开的。作为该方法的用户,我希望它能存在于该库的未来版本中。因此,我希望该方法的输入是
CharSequence
。如果该库的作者想要将其更改为
String
,那么这将是错误的。他们不应将
CharSequence
更改为
String
,因为以前版本的用户期望
CharSequence
,当库的以前用户升级到新版本时,将其切换为
String
可能会产生负面后果。在这种情况下,代码可能无法编译

因此,基本上,如果它不是隐藏的(也就是公共API的一部分),那么作者不应该对公共API进行任何更改,从而使库的早期版本无法工作

这件事一直在发生,而且有很多解决办法。例如,当
log4j
升级到版本2时,它们的变化是如此巨大,以至于它们的API不得不中断,因此创建了一个名为
log4j 2
的全新库。因此,它是一个完全不同的库,具有不同的包名。它不是旧库的新版本,而是具有类似名称的新库

与此形成对比的是,看看JavaSE的
HashTable
类。这是不应该再使用的旧类。但是Java有一条严格的规则,即旧版本的公共API在新版本中仍然必须得到支持。所以Java不能做
log4j
所做的事情

另一个例子是春天。Spring尝试采用Java的方法,只需更新版本,旧代码就可以工作。但是Spring并不赞成它的部分公共API。它将删除旧的类和方法,这些类和方法是公共的,但它确实不希望人们再使用它们。例如,在这种情况下,作为Spring用户的我可能会发现很难从版本1升级到版本4。主要是因为一些公共API可能已被弃用

所以在这里,我给出了图书馆作家应对这种情况的三种不同方式

1) 侧移旧版本并创建一个全新的库。例如Log4j。 2) 严格遵守并始终支持旧的公共AIP。例如Java。
3) 慢慢地弃用旧的公共API方法或类。例如春天


作为这些库的用户,我更希望支持旧的公共API。我已经使用了这三个例子。我不得不在升级到Java8的遗留代码上使用旧的
HashTable
类,很高兴它仍然受到支持。我感受到了从
log4j
1升级到
log4j 2
的痛苦。我还在一个复杂的项目上升级了Spring,并产生了需要解决的负面影响。我可以告诉你,严格遵守旧的公共API对上述库的用户来说是最容易的

例如,您正在使用
public void print(LinkedList<String> names)
{
    for (String name : names)
        System.out.println(name);
}
public void print(ArrayList<String> names)
{
    for (String name : names)
        System.out.println(name);
}
public void print(Iterable<String> names)
{
    for (String name : names)
        System.out.println(name);
}