1. 概述
封装是面向对象编程的核心范式之一,它允许将数据和方法捆绑在类中。但封装本身并不等同于防御性编程。
要实现健壮性,我们需要引入信息隐藏。信息隐藏是一种编程原则,强调限制对内部实现细节的访问。
本文将深入探讨封装和信息隐藏的细节,通过代码示例解析两者的核心差异。
2. 历史背景
"信息隐藏"一词由Parnas在1972年提出,旨在区分过程式编程与模块化编程。Parnas主张数据的具体实现应对外部模块保持不可见。
1973年,Zelis创造了"封装"概念,解释如何通过限制类中底层数据的访问权限来防止意外修改。
1978年,Parnas声称封装和信息隐藏是同义词。但2001年Paul Roger通过代码示例证明了两者差异:封装可以独立于信息隐藏存在。
封装是面向对象编程中的广义概念,通过数据与方法的捆绑实现模块化设计。虽然常被混用,但两者存在本质区别。
3. 封装
根据Paul Roger的定义:封装本质上是将数据与操作该数据的方法捆绑在一起。
3.1. 没有封装的情况
看一个未封装的示例类:
class Book {
public String author;
public int isbn;
}
Book
类的字段使用public
修饰符,导致外部类可直接访问这些字段。
创建BookDetails
类处理图书信息:
class BookDetails {
public String bookDetails(Book book) {
return "author name: " + book.author + " ISBN: " + book.isbn;
}
}
这里Book
作为数据容器,BookDetails
作为操作集合,这种编程方式早已过时且缺乏模块化。
单元测试示例:
@Test
void givenUnencapsulatedClass_whenImplementationClassIsSeparate_thenReturnResult() {
Book myBook = new Book();
myBook.author = "J.K Rowlings";
myBook.isbn = 67890;
BookDetails details = new BookDetails();
String result = details.bookDetails(myBook);
assertEquals("author name: " + myBook.author + " ISBN: " + myBook.isbn, result);
}
这种设计下,Book
类的任何修改都会影响所有依赖它的类。
3.2. 使用封装的情况
用封装重构上述设计:
class BookEncapsulation {
public String author;
public int isbn;
public BookEncapsulation(String author, int isbn) {
this.author = author;
this.isbn = isbn;
}
public String getBookDetails() {
return "author name: " + author + " ISBN: " + isbn;
}
}
通过将实现与类捆绑,代码变得模块化。客户端无需了解实现细节即可调用getBookDetails()
。
单元测试示例:
@Test
void givenEncapsulatedClass_whenDataIsNotHidden_thenReturnResult() {
BookEncapsulation myBook = new BookEncapsulation("J.K Rowlings", 67890);
String result = myBook.getBookDetails();
assertEquals("author name: " + myBook.author + " ISBN: " + myBook.isbn, result);
}
封装虽改进了设计,但字段仍为public
,外部类仍可修改数据。封装对访问修饰符没有严格限制,可自由使用public
、private
或protected
。
✅ 封装的优势:在不破坏外部代码的情况下为类添加新功能。例如新增id
字段:
// ...
public int id = 1;
public BookEncapsulation(String author, int isbn) {
this.author = author;
this.isbn = isbn;
}
public String getBookDetails() {
return "author id: " + id + " author name: " + author + " ISBN: " + isbn;
}
// ...
这些内部变更不会影响客户端代码。但要实现真正的防御性编程,还需结合信息隐藏。
4. 信息隐藏
信息隐藏的核心原则:防止外部直接修改类数据,并提供严格的数据访问/修改规则。
它还能隐藏可能变更的设计实现细节。与封装结合时,可确保代码的模块化。
用信息隐藏改进封装类:
class BookInformationHiding {
private String author;
private int isbn;
private int id = 1;
public BookInformationHiding(String author, int isbn) {
setAuthor(author);
setIsbn(isbn);
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getIsbn() {
return isbn;
}
public void setIsbn(int isbn) {
this.isbn = isbn;
}
public String getBookDetails() {
return "author id: " + id + " author name: " + author + " ISBN: " + isbn;
}
}
关键改进:
- 使用
private
修饰符限制字段访问 - 通过getter/setter控制数据访问方式
- 在构造器中使用setter确保初始化规则
⚠️ 与普通封装不同:信息隐藏必须使用private
修饰符,完全禁止外部直接访问字段。
单元测试示例:
@Test
void givenEncapsulatedClass_whenDataIsHidden_thenReturnResult() {
BookInformationHiding myBook = new BookInformationHiding("J.K Rowlings", 67890);
String result = myBook.getBookDetails();
assertEquals("author id: " + 1 + " author name: " + myBook.getAuthor() + " ISBN: " + myBook.getIsbn(), result);
}
还可通过setter添加严格校验规则,例如禁止负数ISBN:
public void setIsbn(int isbn) {
if (isbn < 0) {
throw new IllegalArgumentException("ISBN can't be negative");
}
this.isbn = isbn;
}
5. 关键区别
维度 | 信息隐藏 | 封装 |
---|---|---|
本质 | 设计原则:隐藏实现细节,防止意外修改 | 面向对象原则:捆绑数据与方法 |
访问修饰符 | 强制使用private |
可自由使用public /private /protected |
目标 | 实现防御性编程 | 实现信息隐藏的手段 |
6. 结论
本文深入剖析了封装与信息隐藏的核心概念,并通过代码示例展示了二者的细微差异。
尽管有观点认为两者同义,但实践证明:封装类时必须应用信息隐藏原则,才能构建真正健壮的系统。
完整示例代码请查阅GitHub仓库。