封裝(Encapsulation)
首先,我們來(lái)看封裝特性。封裝也叫作信息隱藏或者數(shù)據(jù)訪問(wèn)保護(hù)。類通過(guò)暴露有限的訪問(wèn)接口,授權(quán)外部?jī)H能通過(guò)類提供的方式(或者叫函數(shù))來(lái)訪問(wèn)內(nèi)部信息或者數(shù)據(jù)。
下面這段代碼是金融系統(tǒng)中一個(gè)簡(jiǎn)化版的虛擬錢包的代碼實(shí)現(xiàn):
public class Wallet {
private String id;
private long createTime;
private BigDecimal balance;
private long balanceLastModifiedTime;
// ...省略其他屬性...
public Wallet() {
this.id = IdGenerator.getInstance().generate();
this.createTime = System.currentTimeMillis();
this.balance = BigDecimal.ZERO;
this.balanceLastModifiedTime = System.currentTimeMillis();
}
// 注意:下面對(duì)get方法做了代碼折疊,是為了減少代碼所占文章的篇幅
public String getId() { return this.id; }
public long getCreateTime() { return this.createTime; }
public BigDecimal getBalance() { return this.balance; }
public long getBalanceLastModifiedTime() { return this.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.subtract(decreasedAmount);
this.balanceLastModifiedTime = System.currentTimeMillis();
}
}
Wallet 類主要有四個(gè)屬性(也可以叫作成員變量),也就是我們前面定義中提到的信息或者數(shù)據(jù)。其中,id 表示錢包的唯一編號(hào),createTime 表示錢包創(chuàng)建的時(shí)間,balance 表示錢包中的余額,balanceLastModifiedTime 表示上次錢包余額變更的時(shí)間。
參照封裝特性,對(duì)錢包的這四個(gè)屬性的訪問(wèn)方式進(jìn)行了限制。調(diào)用者只允許通過(guò)下面這六個(gè)方法來(lái)訪問(wèn)或者修改錢包里的數(shù)據(jù)。String getId()long getCreateTime()BigDecimal getBalance()long getBalanceLastModifiedTime()void increaseBalance(BigDecimal increasedAmount)void decreaseBalance(BigDecimal decreasedAmount)
對(duì)于封裝這個(gè)特性,我們需要編程語(yǔ)言本身提供一定的語(yǔ)法機(jī)制來(lái)支持。這個(gè)語(yǔ)法機(jī)制就是訪問(wèn)權(quán)限控制。
抽象(Abstraction)
封裝主要講的是如何隱藏信息、保護(hù)數(shù)據(jù),而抽象講的是如何隱藏方法的具體實(shí)現(xiàn),讓調(diào)用者只需要關(guān)心方法提供了哪些功能,并不需要知道這些功能是如何實(shí)現(xiàn)的。
我們利用 Java 中的 interface 接口語(yǔ)法來(lái)實(shí)現(xiàn)抽象特性。調(diào)用者在使用圖片存儲(chǔ)功能的時(shí)候,只需要了解 IPictureStorage 這個(gè)接口類暴露了哪些方法就可以了,不需要去查看 PictureStorage 類里的具體實(shí)現(xiàn)邏輯:
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) { ... }
@Override
public void deletePicture(String pictureId) { ... }
@Override
public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}
抽象這個(gè)概念是一個(gè)非常通用的設(shè)計(jì)思想,并不單單用在面向?qū)ο缶幊讨校部梢杂脕?lái)指導(dǎo)架構(gòu)設(shè)計(jì)等。而且這個(gè)特性也并不需要編程語(yǔ)言提供特殊的語(yǔ)法機(jī)制來(lái)支持,只需要提供“函數(shù)”這一非?;A(chǔ)的語(yǔ)法機(jī)制,就可以實(shí)現(xiàn)抽象特性、所以,它沒(méi)有很強(qiáng)的“特異性”,有時(shí)候并不被看作面向?qū)ο缶幊痰奶匦灾弧?/p>
繼承(Inheritance)
繼承是用來(lái)表示類之間的 is-a 關(guān)系,比如貓是一種哺乳動(dòng)物。從繼承關(guān)系上來(lái)講,繼承可以分為兩種模式,單繼承和多繼承。單繼承表示一個(gè)子類只繼承一個(gè)父類,多繼承表示一個(gè)子類可以繼承多個(gè)父類,比如貓既是哺乳動(dòng)物,又是爬行動(dòng)物。
多態(tài)(Polymorphism)
多態(tài)是指,子類可以替換父類,在實(shí)際的代碼運(yùn)行過(guò)程中,調(diào)用子類的方法實(shí)現(xiàn)。
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() {
//...如果數(shù)組滿了就擴(kuò)容...代碼省略...
}
}
public class SortedDynamicArray extends DynamicArray {
@Override
public void add(Integer e) {
ensureCapacity();
int i;
for (i = size-1; i>=0; --i) { //保證數(shù)組中的數(shù)據(jù)有序
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); // 打印結(jié)果:1、3、5
}
}
多態(tài)也是很多設(shè)計(jì)模式、設(shè)計(jì)原則、編程技巧的代碼實(shí)現(xiàn)基礎(chǔ),比如策略模式、基于接口而非實(shí)現(xiàn)編程、依賴倒置原則、里式替換原則、利用多態(tài)去掉冗長(zhǎng)的 if-else 語(yǔ)句等等。
在上面的例子中,我們用到了三個(gè)語(yǔ)法機(jī)制來(lái)實(shí)現(xiàn)多態(tài)。第一個(gè)語(yǔ)法機(jī)制是編程語(yǔ)言要支持父類對(duì)象可以引用子類對(duì)象,也就是可以將 SortedDynamicArray 傳遞給 DynamicArray。第二個(gè)語(yǔ)法機(jī)制是編程語(yǔ)言要支持繼承,也就是 SortedDynamicArray 繼承了 DynamicArray,才能將 SortedDyamicArray 傳遞給 DynamicArray。第三個(gè)語(yǔ)法機(jī)制是編程語(yǔ)言要支持子類可以重寫(override)父類中的方法,也就是 SortedDyamicArray 重寫了 DynamicArray 中的 add() 方法。
按照What/How/Why 模型梳理:
封裝
What:隱藏信息,保護(hù)數(shù)據(jù)訪問(wèn)。
How:暴露有限接口和屬性,需要編程語(yǔ)言提供訪問(wèn)控制的語(yǔ)法。
Why:提高代碼可維護(hù)性;降低接口復(fù)雜度,提高類的易用性。
抽象
What: 隱藏具體實(shí)現(xiàn),使用者只需關(guān)心功能,無(wú)需關(guān)心實(shí)現(xiàn)。
How: 通過(guò)接口類或者抽象類實(shí)現(xiàn),特殊語(yǔ)法機(jī)制非必須。
Why: 提高代碼的擴(kuò)展性、維護(hù)性;降低復(fù)雜度,減少細(xì)節(jié)負(fù)擔(dān)。
繼承
What: 表示 is-a 關(guān)系,分為單繼承和多繼承。
How: 需要編程語(yǔ)言提供特殊語(yǔ)法機(jī)制。例如 Java 的 “extends”,C++ 的 “:” 。
Why: 解決代碼復(fù)用問(wèn)題。
多態(tài)
What: 子類替換父類,在運(yùn)行時(shí)調(diào)用子類的實(shí)現(xiàn)。
How: 需要編程語(yǔ)言提供特殊的語(yǔ)法機(jī)制。比如繼承、接口類、duck-typing。
Why: 提高代碼擴(kuò)展性和復(fù)用性。
3W 模型的關(guān)鍵在于 Why,沒(méi)有 Why,其它兩個(gè)就沒(méi)有存在的意義。從四大特性可以看出,面向?qū)ο蟮慕K極目的只有一個(gè):可維護(hù)性。易擴(kuò)展、易復(fù)用,降低復(fù)雜度等等都屬于可維護(hù)性的實(shí)現(xiàn)方式