0%

封装、抽象、继承、多态分别可以解决哪些编程问题?

封装(Encapsulation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.math.BigDecimal;

public class Wallet {
private final String id;
private final long createTime;
private final BigDecimal balance;
private long balanceLastModifiedTime;

//...省略其他属性...
public Wallet() {
this.id = IdGenerater.getInstance().generate();
this.createTime = System.currentTimeMillis();
this.balance = BigDecimal.ZERO;
this.balanceLastModifiedTime = System.currentTimeMillis();
}

// 注意:下面对get方法做了代码折叠,是为了减少代码所占文章的篇幅
public String getId() {
return this.id;
}

public long getCreateTime() {
return createTime;
}

public BigDecimal getBalance() {
return balance;
}

public long getBalanceLastModifiedTime() {
return balanceLastModifiedTime;
}

public void increaseBalance(BigDecimal increasedAmount) {
if (increasedAmount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidAmountException("...");
}
this.balance.add(increasedAmount);
this.balanceLastModifiedTime = System.currentTimeMillis();
}

public void decreaseBalance(BigDecimal decreasedAmount) {
if (decreasedAmount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidAmountException("...");
}
if (decreasedAmount.compareTo(this.balance) > 0) {
throw new InsufficientAmountException("...");
}
this.balance.substract(decreasedAmount);
this.balanceLastModifiedTime = System.currentTimeMillis();
}
}

抽象(Abstraction)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.awt.*;

public interface IPictureStorage {
void savePicture(Picture picture);

Image getPicture(String pictureId);

void deletePicture(String pictureId);

void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
}

public class PictureStorage implements IPictureStorage {
// ...省略其他属性...

@Override
public void savePicture(Picture picture) {

}

@Override
public Image getPicture(String pictureId) {
return null;
}

@Override
public void deletePicture(String pictureId) {

}

@Override
public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) {

}
}

继承(Inheritance)

继承的概念很好理解,也很容易使用。不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还需要按照继承关系一层一层地往上查看“父类、父类的父类……”的代码。还有,子类和父类高度耦合,修改父类的代码,会直接影响到子类。

所以,继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式。我们应该尽量少用,甚至不用。

多态(Polymorphism)

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class DynamicArray {
private static final int DEFAULT_CAPACITY = 10;
protected int size = 0;
protected int capacity = DEFAULT_CAPACITY;
protected Integer[] elements = new Integer[DEFAULT_CAPACITY];

public int size() {
return this.size;
}

public Integer get(int index) {
return elements[index];
}

// ...省略n多方法...
public void add(Integer e) {
ensureCapacity();
elements[size++] = e;
}

protected void ensureCapacity() {
// ...如果数组满了就扩容...代码省略...
}

}

public class SortedDynamicArray extends DynamicArray {
@Override
public void add(Integer e) {
ensureCapacity();
int i;
for (i = size - 1; i >= 0; --i) { // 保证数组中的数据有序(插入排序)
if (elements[i] > e) {
elements[i + 1] = elements[i];
} else {
break;
}
}
elements[i + 1] = e;
++size;
}
}

public class Example {
public static void test(DynamicArray dynamicArray) {
dynamicArray.add(5);
dynamicArray.add(1);
dynamicArray.add(3);
for (int i = 0; i < dynamicArray.size(); ++i) {
System.out.println(dynamicArray.get(i));
}
}

public static void main(String[] args) {
DynamicArray dynamicArray = new SortedDynamicArray();
test(dynamicArray); // 打印结果:1、3、5
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.LinkedList;

public interface Iterator {
boolean hasNext();

String next();

String remove();
}

public class Array implements Iterator {
private String[] data;


@Override
public boolean hasNext() {
return false;
}

@Override
public String next() {
return null;
}

@Override
public String remove() {
return null;
}
// ...省略其他方法...
}

public class Demo {
private static void print(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

public static void main(String[] args) {
Iterator arrayIterator = new Array();
print(arrayIterator);

Iterator linkedListIterator = new LinkedList();
print(linkedListIterator);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# duck-typing.py


class Logger:
def record(self):
print("I write a log info file.")


class DB:
def record(self):
print("I insert data into db.")


def test(recorder):
recorder.record()


def main():
logger = Logger()
db = DB()
test(logger)
test(db)


if __name__ == '__main__':
main()

多态特性能提高代码的可扩展性和复用性。为什么这么说呢?我们回过头去看讲解多态特性的时候,举的第二个代码实例(Iterator 的例子)。

在那个例子中,我们利用多态的特性,仅用一个 print() 函数就可以实现遍历打印不同类型(Array、LinkedList)集合的数据。当再增加一种要遍历打印的类型的时候,比如 HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next() 等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性。

如果我们不使用多态特性,我们就无法将不同的集合类型(Array、LinkedList)传递给相同的函数(print(Iterator iterator) 函数)。我们需要针对每种要遍历打印的集合,分别实现不同的 print() 函数,比如针对 Array,我们要实现 print(Array array) 函数,针对 LinkedList,我们要实现 print(LinkedList linkedList) 函数。而利用多态特性,我们只需要实现一个 print() 函数的打印逻辑,就能应对各种集合数据的打印操作,这显然提高了代码的复用性。

除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。关于这点,在学习后面的章节中,你慢慢会有更深的体会。

小结

  1. 关于封装特性

    封装也叫做信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如Java中的private、protected、public关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。

  2. 关于抽象特性

    封装主要讲如何隐藏信息、保护数据,那抽象就是将如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类来实现,但也并不需要特殊的语言机制来支持。抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。

  3. 关于继承特性

    继承是用来表示类之间的is-a关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。

  4. 关于多态特性

    多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。

    ## 封装 What:隐藏信息,保护数据访问。 How:暴露有限接口和属性,需要编程语言提供访问控制的语法。 Why:提高代码可维护性;降低接口复杂度,提高类的易用性。

    ##抽象 What: 隐藏具体实现,使用者只需关心功能,无需关心实现。 How: 通过接口类或者抽象类实现,特殊语法机制非必须。 Why: 提高代码的扩展性、维护性;降低复杂度,减少细节负担。

    ##继承 What: 表示 is-a 关系,分为单继承和多继承。 How: 需要编程语言提供特殊语法机制。例如 Java 的 “extends”,C++ 的 “:” 。 Why: 解决代码复用问题。

    ##多态 What: 子类替换父类,在运行时调用子类的实现。 How: 需要编程语言提供特殊的语法机制。比如继承、接口类、duck-typing。 Why: 提高代码扩展性和复用性。

    3W 模型的关键在于 Why,没有 Why,其它两个就没有存在的意义。从四大特性可以看出,面向对象的终极目的只有一个:可维护性。易扩展、易复用,降低复杂度等等都属于可维护性的实现方式。