《設計模式之美》二 面向對象(1)

設計原則與思想:面向對象(11講)

1. 什麼是面向對象編程(OOP)

  • 面向對象編程是一種編程範式編程風格。它以類或對象做爲組織代碼的基本單元,並將封裝、抽象、繼承、多態四個特性,做爲代碼設計和實現的基石

2. 什麼是面向對象編程語言

  • 面向對象編程語言是支持類或對象的語法機制,並有現成的語法機制,能方便地實現面向對象編程四大特性(封裝、抽象、繼承、多態)的編程語言

3. 面向對象編程和麪向對象編程語言之間的關係

  • 面向對象編程通常使用面向對象編程語言來進行,可是,不用面向對象編程語言,咱們照樣能夠進行面向對象編程。反過來說,即使咱們使用面向對象編程語言,寫出來的代碼也不必定是面向對象編程風格的,也有多是面向過程編程風格的

4. UML 統一建模語言

  • 學習成本較高,能夠適當簡單學習

5. 封裝、繼承、多態、抽象分別解決哪些編程問題

* 封裝
  • 概念:封裝也叫做信息隱藏或者數據訪問保護經過暴露有限的訪問接口,受權外部僅能經過類提供的方法(函數)來訪問內部的信息和數據
  • 做用:
  1. 保護數據不被隨意修改,提升代碼的可維護性
  2. 僅暴露有限的必要接口,提升類的易用性
  • 封裝特性,必須使用訪問控制權限
  • 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();
  }
}
  • 解析
    1. 從業務的角度來講,id、createTime 在建立錢包的時候就肯定好了,以後不該該再被改動,因此,咱們並無在 Wallet 類中,暴露 id、createTime 這兩個屬性的任何修改方法,好比 set 方法。因此,在 Wallet 類的構造函數內部將其初始化設置好,而不是經過構造函數的參數來外部賦值。
    1. 對於錢包餘額 balance 這個屬性,從業務的角度來講,只能增或者減,不會被從新設置。因此,咱們在 Wallet 類中,只暴露了 increaseBalance() 和 decreaseBalance() 方法,並無暴露 set 方法。對於 balanceLastModifiedTime 這個屬性,它徹底是跟 balance 這個屬性的修改操做綁定在一塊兒的。只有在 balance 修改的時候,這個屬性纔會被修改。因此,咱們把 balanceLastModifiedTime 這個屬性的修改操做徹底封裝在了increaseBalance() 和 decreaseBalance() 兩個方法中,不對外暴露任何修改這個屬性的方法和業務細節。這樣也能夠保證 balance 和 balanceLastModifiedTime 兩個數據的一致性。
抽象
  • 概念: 抽象是如何隱藏方法的具體實現,讓使用者只須要關心調用哪些方法,不須要知道具體如何實現
  • 抽象能夠經過接口類(Java中的interface關鍵字) 或 抽象類(Java中的abstract關鍵字)來實現
  • 做用:
  1. 提升代碼的可擴展性、維護性,修改時不須要改變定義,減小代碼的改動範圍
  2. 抽象是處理複雜系統的有效手段,能有效過濾掉沒必要要關注的信息
  • Demo: 圖片存儲功能
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) { ... }
}
  • 解析
    1. 在上面的這段代碼中,咱們利用 Java 中的 interface 接口語法來實現抽象特性
    1. 調用者在使用圖片存儲功能的時候,只須要了解 IPictureStorage 這個接口類暴露了哪些方法就能夠了,不須要去查看 PictureStorage 類裏的具體實現邏輯
    1. 舉個簡單例子,好比 getAliyunPictureUrl() 就不是一個具備抽象思惟的命名,若是更換存儲設備,那這個命名也要隨之被修改。相反,若是咱們定義一個比較抽象的函數,好比叫做 getPictureUrl(),那即使內部存儲方式修改了,咱們也不須要修改命名。
繼承
  • 概念:繼承用來表示類與類之間is-a關係,可分爲單繼承和多繼承
  • 實現繼承,須要語言的語法支持,Java 使用 extends 關鍵字來實現繼承,C++ 使用冒號(class B : public A),Python 使用 paraentheses(),Ruby 使用 <
  • 單繼承:Java、PHP、C#、Ruby 等
  • 多繼承:C++、Python、Perl 等
  • 做用: 提升代碼複用性,好比通用的方法抽象到父類
  • 缺點: 過分使用繼承,繼承層次過深、過複雜,就會致使代碼的可讀性、可維護性變差
  • 解決思路:多用組合少用繼承
多態
  • 概念:父類,被多個子類繼承,若是父類的某個方法,在多個子類中被重寫,表現出不一樣功能,就是多態(同一個類的不一樣子類表現不一樣形態)
  • 實現方法:
    1. 繼承+方法重寫 -Demo1
    1. 利用接口類語法(C++不支持) -Demo2
    1. 利用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多繼承的實現方法
針對多繼承時,多個父類的同名方法, Python採用MRO,在多繼承時,判斷方法、屬性的調用路徑 在當前類中找到方法,就直接執行,再也不搜索;若是沒有找到,就查找下一個類是否有對應方法,若是找到就直接執行,再也不搜索;直到最後一個類,沒找到就報錯
相關文章
相關標籤/搜索