設計模式 之: 封裝、抽象、繼承、多態

封裝

隱藏對象的屬性和實現細節,僅對外公開接口,控制程序中屬性的讀取和修改的訪問級別


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();
  }

  // 注意:下面對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();
  }
}

如上所示,id,balance等屬性沒法直接修改的,只能通過方法修改數據(是否提供修改數據的方法,和業務相關)或者獲取數據

封裝是通過訪問權限控制的語法機制來支持實現的,例如,id,balance屬性都是使用private來修飾的,其它類不能直接訪問它

封裝解決了什麼問題

對類的屬性做一些訪問權限控制,可以增加代碼的維護和可控性,只增加一些必要的方法給調用者,也提高了類的易用性

 

抽象

抽象主要是如何隱藏方法的具體實現,讓調用者只關心提供了那些功能,而不需要知道這些功能是怎麼實現的

抽象一般使用編程語言提供的接口類和抽象類這兩個語法機制來實現


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 {
  // ...省略其他屬性...
  public void savePicture(Picture picture) { ... }
  public Image getPicture(String pictureId) { ... }
  public void deletePicture(String pictureId) { ... }
  public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}

上例代碼中,調用者只需調用圖片的相關方法,而不需要知道具體的實現

抽象解決了什麼問題

對於複雜的系統設計,我們可以忽略掉非關鍵性的實現細節,只需關注功能點就好

 

繼承

繼承用來表示類之間是is-a的關係,子類可以繼承父類的屬性和方法

繼承解決了什麼問題

繼承最大的好處就是代碼複用,如果兩個類有相同的屬性和方法,可以將相同的部分抽取到父類中,然後這兩個類繼承父類,

多態

多態是指子類可以替換父類,在實際代碼運行過程中,調用子類的實現


public class DynamicArray {
  private static final int DEFAULT_CAPACITY = 10;
  private int size = 0;
  private int capacity = DEFAULT_CAPACITY;
  private 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();
    for (int 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[i]);
    }
  }
  
  public static void main(String args[]) {
    DynamicArray dynamicArray = new SortedDynamicArray();
    test(dynamicArray); // 打印結果:1、3、5
  }
}

上述例子中,使用了三個語法機制來實現多態

第一個語法機制是編程語言要支持父類對象可以引用子類對象,也就是可以將 SortedDynamicArray 傳遞給 DynamicArray。

第二個語法機制是編程語言要支持繼承,也就是 SortedDynamicArray 繼承了 DynamicArray,才能將 SortedDyamicArray 傳遞給 DynamicArray。

第三個語法機制是編程語言要支持子類可以重寫(override)父類中的方法,也就是 SortedDyamicArray 重寫了 DynamicArray 中的 add() 方法

多態的實現方式,除了上述的“繼承加方法重寫”這種方式之外,還有其他比較常見兩種方式,一個是利用接口類語法,另一個是利用 duck-typing 語法

接口類實現多態特性


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);
  }
}

用 duck-typing 來實現多態特性


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 這樣的靜態語言,通過繼承實現多態特性,必須要求兩個類之間有繼承關係,通過接口實現多態特性,類必須實現對應的接口。

多態解決了什麼問題

多態能提高代碼的複用性和擴展性

 

參考資料

https://time.geekbang.org/column/article/161114

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章