設計原則與思想:面向對象(11講)
1. 什麼是面向對象編程(OOP)
- 面向對象編程是一種
編程範式
或編程風格
。它以類或對象
做爲組織代碼的基本單元,並將封裝、抽象、繼承、多態四個特性
,做爲代碼設計和實現的基石
2. 什麼是面向對象編程語言
- 面向對象編程語言是
支持類或對象的語法機制
,並有現成的語法機制,能方便地實現面向對象編程四大特性
(封裝、抽象、繼承、多態)的編程語言
3. 面向對象編程和麪向對象編程語言之間的關係
- 面向對象編程通常使用面向對象編程語言來進行,可是,不用面向對象編程語言,咱們照樣能夠進行面向對象編程。反過來說,即使咱們使用面向對象編程語言,寫出來的代碼也不必定是面向對象編程風格的,也有多是面向過程編程風格的
4. UML 統一建模語言
5. 封裝、繼承、多態、抽象分別解決哪些編程問題
* 封裝
- 概念:封裝也叫做
信息隱藏
或者數據訪問保護
,類
經過暴露有限的訪問接口
,受權外部僅能
經過類提供的方法(函數)
來訪問內部的信息和數據
- 做用:
- 保護數據不被隨意修改,提升代碼的
可維護性
- 僅暴露有限的必要接口,提升
類的易用性
- 封裝特性,必須使用
訪問控制權限
- Demo: 金融系統簡化版虛擬錢包
public class Wallet {
private String id;//錢包惟一編號
private long createTime;//錢包建立時間
private BigDecimal balance;//錢包餘額
//BigDecimal 對數字進行精度計算,返回的是對象,不能經過傳統+ - * / 計算,而是必須調用相應的方法
private long balanceLastModifiedTime;//上次錢包餘額變動時間
//構造函數,完成對象初始化。Java構造函數的名稱必須與類名相同,包括大小寫;構造函數沒有返回值,也不能用void修飾;
public Wallet() {
this.id = IdGenerator.getInstance().generate();//全局惟一ID生成器
this.createTime = System.currentTimeMillis();
this.balance = BigDecimal.ZERO;//0 用於和0比較大小
this.balanceLastModifiedTime = System.currentTimeMillis();//獲取時間,毫秒級
}
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();
}
}
- 解析
-
- 從業務的角度來講,id、createTime 在建立錢包的時候就肯定好了,以後不該該再被改動,因此,咱們並無在 Wallet 類中,暴露 id、createTime 這兩個屬性的任何修改方法,好比 set 方法。因此,
在 Wallet 類的構造函數內部將其初始化設置好
,而不是經過構造函數的參數來外部賦值。
-
- 對於錢包餘額 balance 這個屬性,從業務的角度來講,只能增或者減,不會被從新設置。因此,咱們在 Wallet 類中,只暴露了 increaseBalance() 和 decreaseBalance() 方法,並無暴露 set 方法。對於 balanceLastModifiedTime 這個屬性,它徹底是跟 balance 這個屬性的修改操做綁定在一塊兒的。只有在 balance 修改的時候,這個屬性纔會被修改。因此,咱們把 balanceLastModifiedTime 這個屬性的修改操做徹底
封裝
在了increaseBalance() 和 decreaseBalance() 兩個方法中,不對外暴露任何修改這個屬性的方法
和業務細節。這樣也能夠保證 balance 和 balanceLastModifiedTime 兩個數據的一致性。
抽象
- 概念: 抽象是如何隱藏方法的具體實現,讓使用者只須要關心調用哪些方法,不須要知道具體如何實現
- 抽象能夠經過
接口類
(Java中的interface關鍵字) 或 抽象類
(Java中的abstract關鍵字)來實現
- 做用:
- 提升代碼的可擴展性、維護性,修改時不須要改變定義,減小代碼的改動範圍
- 抽象是處理複雜系統的有效手段,能有效過濾掉沒必要要關注的信息
public interface IPictureStorage {
// void 是空,在方法聲明中表示該方法沒有返回值
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) { ... }
}
- 解析
-
- 在上面的這段代碼中,咱們利用 Java 中的 interface 接口語法來實現抽象特性
-
- 調用者在使用圖片存儲功能的時候,只須要了解 IPictureStorage 這個接口類暴露了哪些方法就能夠了,不須要去查看 PictureStorage 類裏的具體實現邏輯
-
- 舉個簡單例子,好比 getAliyunPictureUrl() 就不是一個具備抽象思惟的命名,若是更換存儲設備,那這個命名也要隨之被修改。相反,若是咱們定義一個比較抽象的函數,好比叫做 getPictureUrl(),那即使內部存儲方式修改了,咱們也不須要修改命名。
繼承
- 概念:繼承用來表示
類與類之間is-a關係
,可分爲單繼承和多繼承
- 實現繼承,須要語言的語法支持,Java 使用
extends
關鍵字來實現繼承,C++ 使用冒號
(class B : public A),Python 使用 paraentheses()
,Ruby 使用 <
- 單繼承:Java、PHP、C#、Ruby 等
- 多繼承:C++、Python、Perl 等
- 做用: 提升
代碼複用性
,好比通用的方法抽象到父類
- 缺點: 過分使用繼承,繼承層次過深、過複雜,就會致使代碼的可讀性、可維護性變差
- 解決思路:
多用組合少用繼承
多態
- 概念:
父類,被多個子類繼承,若是父類的某個方法,在多個子類中被重寫,表現出不一樣功能,就是多態(同一個類的不一樣子類表現不一樣形態)
- 實現方法:
-
-
繼承+方法重寫
-Demo1
-
- 利用
接口類
語法(C++不支持) -Demo2
-
- 利用
duck-typing
語法(僅有 Python、JavaScript支持) -Demo3
- 做用: 多態能夠提升代碼的
擴展性
和複用性
- Demo1
//繼承+方法重寫
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));
}
}
//main()方法是Java應用程序入口方法,程序運行,第一個執行的方法就是main()方法
public static void main(String args[]) {
DynamicArray dynamicArray = new SortedDynamicArray();
test(dynamicArray); // 打印結果:一、三、5
}
}
- 解析
- 多態這種特性也須要編程語言提供特殊的語法機制來實現。在上面的例子中,咱們用到了三個語法機制來實現多態。
- 第一,支持父類對象引用子類對象,也就是能夠將 SortedDynamicArray 傳遞給 DynamicArray。
- 第二,支持繼承,也就是 SortedDynamicArray 繼承了 DynamicArray,才能將 SortedDyamicArray 傳遞給 DynamicArray
- 第三,支持子類能夠重寫(override)父類中的方法,也就是 SortedDyamicArray 重寫了 DynamicArray 中的 add() 方法。
- 經過這三種語法機制配合在一塊兒,咱們就實現了在 test() 方法中,子類 SortedDyamicArray 替換父類 DynamicArray,執行子類 SortedDyamicArray 的 add() 方法,也就是實現了多態特性。
- Demo2
//利用接口類實現
public interface Iterator {
String hasNext();//檢測序列是否還有元素
String next();//獲取序列下一個元素
String remove();//將迭代器新返回的元素刪除
}
public class Array implements Iterator {
private String[] data;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//...省略其餘方法...
}
public class LinkedList implements Iterator {
private LinkedListNode head;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//...省略其餘方法...
}
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);
}
}
- 解析
- 在這段代碼中,Iterator 是一個接口類,定義了一個能夠遍歷集合數據的迭代器。Array 和 LinkedList 都實現了接口類 Iterator
- 咱們經過傳遞不一樣類型的實現類(Array、LinkedList)到 print(Iterator iterator) 函數中,支持動態的調用不一樣的 next()、hasNext() 實現。
- Demo3
class Logger:
def record(self):
print(「I write a log into file.」)
class DB:
def record(self):
print(「I insert data into db. 」)
def test(recorder):
recorder.record()
def demo():
logger = Logger()
db = DB()
test(logger)
test(db)
- 解析
- 從這段代碼中看出,duck-typing 實現多態的方式很是靈活。Logger 和 DB 兩個類沒有任何關係,既不是繼承關係,也不是接口和實現的關係,可是隻要它們都有定義了 record() 方法,就能夠被傳遞到 test() 方法中
- 在實際運行的時候,執行對應的 record() 方法。也就是說,只要兩個類具備相同的方法,就能夠實現多態,並不要求兩個類之間有任何關係,這就是所謂的 duck-typing,是一些動態語言所特有的語法機制。而像 Java 這樣的靜態語言,經過繼承實現多態特性,必需要求兩個類之間有繼承關係,經過接口實現多態特性,類必須實現對應的接口。
- 擴展:
- Java,PHP不支持多繼承緣由:
多重繼承有反作用:
菱形繼承(鑽石問題)
假設類 B 和類 C 繼承自類 A,且都重寫了類 A 中的同一個方法,而類 D 同時繼承了類 B 和類 C,那麼此時類 D 會繼承 B、C 的方法,那對於 B、C 重寫的 A 中的方法,類 D 會繼承哪個呢?這裏就會產生歧義。
針對多繼承時,多個父類的同名方法,
Python採用MRO
,在多繼承時,判斷方法、屬性的調用路徑 在當前類中找到方法,就直接執行,再也不搜索;若是沒有找到,就查找下一個類是否有對應方法,若是找到就直接執行,再也不搜索;直到最後一個類,沒找到就報錯