四大特性

封装

概念

封装(Encapsulation) 也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。

目的

预防不可控

对类中属性的访问不做限制,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。

提高易用性

如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。相反,如果我们将属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。

实现

通过语言本身的访问 权限控制 这一语法机制支持封装


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

抽象

概念

抽象(Abstraction) 是指:隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

目的

忽略细节

在面对复杂系统的时候,人脑能承受的信息复杂程度是有限的,所以我们必须忽略掉一些非关键性的实现细节。而抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。

变实现不变定义

修改方法实现逻辑时不用修改定义,对调用者友好。

实现

借助编程语言提供的 接口类(比如 Java 中的 interface 关键字语法)或者 抽象类(比如 Java 中的 abstract 关键字语法)这两种语法机制,来实现抽象这一特性。


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

继承

概念

继承(Inheritance) 用于表示类之间的 is-a 关系。

目的

代码复用

将相同的属性和方法,抽取到父类中,子类就可以重用父类中的代码,避免代码重复写多遍。不过,也可以通过组合关系来解决这个代码复用的问题。

反应关系

通过继承来关联两个类,反应真实世界中的 is-a 关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。

弊端

过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。

实现

为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如 Java 使用 extends 关键字来实现继承。有些编程语言只支持单继承,不支持多重继承,而有些编程语言既支持单重继承,也支持多重继承。

组合优于继承

利用 组合(composition)接口委托(delegation) 三个技术手段,一块儿来解决继承存在的问题。


public interface Flyable {
  void fly();
}
public interface Tweetable {
  void tweet();
}
public interface EggLayable {
  void layEgg();
}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
public class TweetAbility implements Tweetable {
  @Override
  public void tweet() { //... }
}
public class EggLayAbility implements EggLayable {
  @Override
  public void layEgg() { //... }
}

// 鸵鸟不会飞,不用实现飞行接口
public class Ostrich implements Tweetable, EggLayable {
  private TweetAbility tweetAbility = new TweetAbility(); //组合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
  //... 省略其他属性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委托
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委托
  }
}

组合 or 继承

简单用继承,复杂用组合。 如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。
无继承关系用组合。 需要代码复用,又不具有继承关系,使用组合就更加合理、更加灵活
需要多态特性用继承。 如果不能改变一个函数的入参类型,而入参又非接口,为了支持多态,只能采用继承来实现

多态

概念

多态(Polymorphism) 是指,定义中的父类参数,在调用时传入子类,则在实际的代码运行过程中,运行子类的方法实现。

目的

性能提高代码的可扩展性和复用性

利用多态的特性,可以通过一个方法,实现操作不同类型的数据。新增类型的时候,只需要创建对应的子类,不用修改操作方法。

多个设计模式的实现基础

很多设计模式、设计原则、编程技巧都需要用到多态特性,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。

实现

继承重写


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

子类 SortedDyamicArray 替换父类 DynamicArray ,执行子类 SortedDyamicArrayadd() 方法,也就是实现了多态特性。

利用接口类


public interface Iterator {
  boolean hasNext();
  String next();
  String remove();
}

public class Array implements Iterator {
  private String[] data;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法...
}

public class LinkedList implements Iterator {
  private LinkedListNode head;
  
  public boolean 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 是一个接口类,定义了一个可以遍历集合数据的迭代器。
  • ArrayLinkedList 都实现了接口类 Iterator
  • 通过传递不同类型的实现类(ArrayLinkedList)到 print(Iterator iterator) 函数中,支持动态的调用不同的 next()hasNext() 实现。

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 实现多态的方式非常灵活
  • 既不是继承关系,也不是接口和实现的关系的类,但是只要它们都有定义了 record() 方法,就可以被传递到 test() 方法中,在实际运行的时候,执行对应的 record() 方法。
  • duck-typing,是一些动态语言所特有的语法机制。