我坚持这个概念。
这是我在网站上看到的解释的一部分:
隐藏实施
面向对象设计的一个主要考虑因素是将变化的东西与保持不变的东西分开。 这对图书馆尤为重要。该库的用户(客户端程序员)必须能够依赖他们使用的部分,并且知道如果新版本的库出现,他们将不需要重写代码。另一方面,库创建者必须能够自由地进行修改和改进,确保客户端代码不会受到这些更改的影响。 这可以通过惯例来实现。例如,库程序员必须同意在修改库中的类时不删除现有方法,因为这会破坏客户端程序员的代码。然而,相反的情况更棘手。对于字段,库创建者如何知道客户端程序员访问了哪些字段?对于仅作为类实现的一部分的方法也是如此,并且不应由客户端程序员直接使用。但是如果图书馆创建者希望撕掉一个旧的实现并放入一个新的实现呢?更改任何这些成员可能会破坏客户端程序员的代码。因此,图书馆创作者穿着紧身衣,不能改变任何东西。
但我无法想象会发生这种情况的真实情况。
有人能在现实生活中向我展示一个实际的例子吗?
这就是我的想象:
public void print(String message)
{
System.out.println(message);
}
如果库客户端知道此实现,它会有什么不同?
答案 0 :(得分:0)
一个简单的例子可能是使用log
方法来记录错误。也许首先它所做的就是打印到控制台。然后你想把错误写入文件:
public void log(String message)
{
// write to file
}
之后您决定写入数据库或拨打远程电话:
public void log(String message)
{
// make network call
}
只要隐藏了实现,客户端就可以继续调用log()
,因为您没有更改方法签名,所以没有任何内容会中断。
您还可以通过提取接口来实现此方法的多种实现,如@tima。
所示答案 1 :(得分:0)
实现并非真正隐藏在视图之外。特别是因为您将使用的大多数库都是开源的。 "隐藏",在这种情况下,指的是不属于公共API的任何内容。库的公共API由库公开的公共方法和字段组成。一旦库发布了版本,它将继续支持未来版本的公共API。其他任何东西都被认为是隐藏的,该库的用户不能依赖于那个"隐藏代码"在该库的未来版本中存在。
编辑:
但是,如果没有隐藏代码,客户端如何依赖代码呢?
因此,假设一个库出现了一个类似于此的方法
public void doSomething(CharSequence x);
这被认为不是隐藏的,因为它是公开的。作为该方法的用户,我希望它存在于该库的未来版本中。所以我希望该方法的输入是CharSequence
。如果该库的作者想要将其更改为String
那么那就错了。他们不应将CharSequence
更改为String
,因为之前版本的用户期望CharSequence
并将其切换为String
可能会对以前的用户产生负面影响库升级到新版本。在这种情况下,代码可能无法编译。
所以基本上,如果它没有被隐藏(也就是公共API的一部分),那么作者不应对公共API进行任何更改,这会使以前版本的库无效。
这种情况一直存在,并且有各种各样的方法。例如,当log4j
升级到版本2时,他们的更改非常引人注目,以至于他们的API必须中断,因此创建了一个名为log4j 2
的全新库。所以它是一个完全不同的库,具有不同的包名。它不是旧库的新版本,它是一个名称相似的新库。
与此形成对比的是,请查看Java SE的HashTable
类。这是不应该再使用的旧类。但是Java有一个严格的规则,即新版本仍然必须支持以前版本的旧公共API。所以Java无法做log4j
所做的事。
另一个例子是Spring。 Spring试图遵循Java所做的方法,因为你只需要更新版本,你的旧代码就可以工作。但Spring确实弃用了部分公共API。它将删除公开的旧类和方法,但它实际上不希望人们再使用它们。在这种情况下,作为Spring的用户,我可能会发现很难从版本1升级到版本4。主要是因为某些公共API可能已被弃用。
所以我在这里给出了图书馆编写者解决这种情况的三种不同方式。
1)Side步骤旧版本并创建一个全新的库。例如。 Log4j的。
2)严格并始终支持老公众AIP。例如。 Java的。
3)慢慢弃用旧的公共API方法或类。例如。弹簧。
作为这些库的用户,我更喜欢支持旧的公共API。我已经使用了所有这三个例子。我不得不使用遗留代码的旧HashTable
类我正在升级到Java 8,并且很高兴它仍然受到支持。我感受到从log4j
1升级到log4j 2
的痛苦。我还在一个复杂的项目上升级了Spring,并且产生了需要排除故障的负面影响。我可以告诉你,严格对待旧的公共API对于所述库的用户来说是最容易的。
答案 2 :(得分:0)
比如说,您正在使用LinkedList
个名称,并且您创建了一个实用程序方法来打印列表中的所有名称:
public void print(LinkedList<String> names)
{
for (String name : names)
System.out.println(name);
}
此有效,但它仅限于使用LinkedList
。如果出于某种原因,您使用LinkedList
缓慢运行的某些其他代码,那么您将其更改为ArrayList
该怎么办?好吧,现在你必须改变这个方法。
public void print(ArrayList<String> names)
{
for (String name : names)
System.out.println(name);
}
但是在这种方法中,如果它是LinkedList
或ArrayList
并不重要,只要你可以迭代它。这是您希望通过使用接口隐藏代码中的实现细节的地方:
public void print(Iterable<String> names)
{
for (String name : names)
System.out.println(name);
}
现在,你甚至不必传递List
- 你可以传递HashSet
,它仍然有用。所以,从你原来的qoute:
面向对象设计的一个主要考虑因素是将变化的东西与保持不变的东西分开
在这种情况下,更改的内容将是如何迭代集合,由ArrayList
和LinkedList
实现表示。
保持不变的是可以迭代集合,这是Iterable
接口所代表的内容。
答案 3 :(得分:0)
现实世界中有很多例子。例如,如果您考虑购买杂货以前v / s现在购买杂货。或者先建造一座房子,然后现在建房子。
随着时间的推移,任何事情的实施变化,但最终的结果仍然像买杂货或建房子一样。因此,库必须具有buyGrocery()
或buildHouse()
等方法,其实现不断变化,但库的用户仍然调用相同的方法来获得最终结果。希望这能解答您的疑问。
干杯!