Java面向对象编程之 封装性 | Java OOP Encapsulation

Made by Mike_Zhang


Java主题:


0. OOP Introduction

0. 面向对象编程介绍

请阅读本博客的Java面向对象编程详细介绍 - Java OOP Detailed Introduction


1. Definition

1. 定义
封装性 (信息隐藏, Encapsulation) 是OOP的核心思想之一,是将对象的属性和方法打包在一起,并且对其用户隐藏对象内部的实现细节,只留给用户可以操作对象的方法。1

Encapsulation - Principles of object-oriented design (Goodrich et al., 2014). 2

如上图,可以看做一个控制系统对象。程序员把此对象的属性(时间、信号等)与方法(开关、通信、计时等)打包在一个盒子中。留给目标用户的只有最上层的一个开关,用户只需要(只能)操作这个开关就能控制这个系统,无需知道哪内部是如何运作的,也不能对内部的元件进行操作,实现的具体内容留给程序员。


2. Implementations

2. 实现方法

实现封装性的关键是提供方法(Methods)给用户去访问属性(Fields)绝不提供给用户直接访问属性的途径。外部的程序只能通过对象的方法与其内部的属性进行交互。1

封装一个成员属性的具体步骤:

  1. 定义一个private属性
  2. 定义一个public属性访问器(accessor) 方法;
  3. 定义一个public属性修改器(mutator) 方法;

常见的应用场景:

  • 只读属性(Read-only field):
    • 确保某一属性只读,一旦被构造函数被赋值,即不能再被用户改动。否则用户可以随意修改属性,会造成混乱:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
// 私有只读属性name。只有类内部可访问和修改,对用户隐藏。
private String name;

public Person(String initName) {
name = initName;
}

// 访问器方法getName。留给用户获取name属性的唯一途径,确保只读,不会被用户随意修改。
public String getName() {
return name;
}
}
    • 若用户需要访问某对象的姓名,则:
1
2
3
4
Person p1 = new Person("Alice");
// Not allowed
// System.out.println(p1.name);
System.out.println(p1.getName());

  • 特定条件更新属性:
    • 确保某一属性按照特定条件更新,否则会造成混乱:
1
2
3
4
5
6
7
8
class Person {
private int age; // 私有属性age
// ...
public void growUp() { // 修改器方法
// 长大意味着年龄增加。不能让用户随意修改年龄,否则会出现逆龄生长的混乱。
age++;
}
}
    • 若用户需要让对象长大,则:
1
2
3
4
5
Person p1 = new Person("Alice");
// Not allowed
// p1.age--
// p1.age = 18;
p1.growUp();

3. Benefits

3. 好处

3.1 Security

3.1 确保数据安全

上文提到,属性被设为private,用户以及外部程序无法直接访问或修改属性,必须通过设计的特定方法进行访问和修改,确保了数据的安全,例如:

1
2
3
4
class Person {
public int age; // 公共属性age
// ...
}

若设置age属性为公共,用户便可以直接访问并修改age属性,可能会造成错误,例如:

1
2
3
Person p1 = new Person();
p1.age = -1; // Unreasonable
p1.age = "21"; // Wrong data type

3.2 Reduce Effects from Changing

3.2 减少修改带来的影响

若类内部的实现方法需要发生改变,封装性能够减少此改变对其他类成员、用户、外部程序的影响,例如:1

我需要修改上述例子中name这一属性,变成两部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
// 修改前
// private String name;
// ...
// public String getName() {
// return name;
// }

// 修改后
private String firstName;
private String lastName;
// ...
public String getName() {
return firstName+" "+lastName;
}
}

从用户的视角对比一下封装带来的好处:

  • 无封装的情况:

    • 此情况下,name等属性为公开,可被直接访问。
1
2
3
4
5
6
Person p1 = new Person("Alice");
// 修改前
System.out.println(p1.name);

// 修改后。需要修改表达式,否则访问name属性会造成错误,因为此对象已被修改,无name这一属性
System.out.println(p1.firstName+" "+p1.lastName);
  • 有封装的情况:
1
2
3
4
Person p1 = new Person("Alice");
// 用户不用做任何修改。只需调用getName方法便可获取姓名。
// 至于内部如何获取、有何实现的改变,用户不用担心,全部交给getName方法内部处理
System.out.println(p1.getName());

3.3 Error Checking

3.3 提供错误检查

某些属性值需要符合特定的要求,如,年龄需要大于0,薪资不能为负数等。
若直接让用户访问和修改属性,则可能会出现违反要求的情况,会造成错误。因此封装能确保用户在这些要求下进行操作,并及时检查错误行为,例如:1

  • 无封装的情况:
1
2
3
4
class Person {
public int age; // 公共属性age
// ...
}
1
2
3
Person p1 = new Person();
// No warning, but unreasonable
p1.age = -20;
  • 有封装的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
private int age; // 私人属性age
// ...

public void growUp() {
age++;
}

public void setAge(int inAge) {
if (inAge > 0) {
age = inAge;
}
else {
System.out.println("Invalid input!");
}
}
}
1
2
3
4
5
6
Person p1 = new Person();
// Not allowed
// p1.age = -1;
p1.growUp(); // OK
p1.setAge(-20); // Warning: "Invalid input!"
p1.setAge(20); // OK

会发现封装性有一点“绕圈子”,明明可以直接访问属性,却要用一个额外的方法去返回这个属性。
确实,如果你的对象只需要实现一些很简单的功能,不会有后续的改动,不会有外部的用户,你无需使用封装,你甚至无需使用OOP。

但是,如果你的功能复杂度很高,用户很多,需要一直改动,不断添加新的功能,那好的封装会提高你代码的复用性、可靠性和安全性等。


References

引用

1. C. S. Horstmann, Core Java. Boston: Pearson, 2019.
2. M. Goodrich, R. Tamassia, and A. O’reilly, Data Structures and Algorithms in Java, 6th Edition. John Wiley & Sons, 2014.

Outro

尾巴
Java中OOP相关的知识是十分重要的, 会继续更新.
最后,希望大家一起交流,分享,指出问题,谢谢!


原创文章,转载请标明出处
Made by Mike_Zhang




感谢你的支持

Java面向对象编程之 封装性 | Java OOP Encapsulation
https://ultrafish.io/post/Java-oop-encapsulation/
Author
Mike_Zhang
Posted on
September 2, 2022
Licensed under