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,外部类仍可修改数据。封装对访问修饰符没有严格限制,可自由使用publicprivateprotected

✅ 封装的优势:在不破坏外部代码的情况下为类添加新功能。例如新增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;
    }
}

关键改进:

  1. 使用private修饰符限制字段访问
  2. 通过getter/setter控制数据访问方式
  3. 在构造器中使用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仓库


原始标题:Difference Between Information Hiding and Encapsulation