面向对象

125 阅读7分钟

什么是面向对象

  • 面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。

面向对象编程语言是支持类或对象的语法机制,并有现成语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言

什么是面向对象编程语言?

1、支持类或对象的语法机制 2、有现成的语法机制,能方便的实现 封装、抽象、继承、多态

面向对象软件开发的三个阶段

  • OOP 面向对象编程 (Object Oriented Programming)
  • OOA 面向对象分析(Object Oriented Analysis)
  • OOD 面向对象设计 (Object Oriented Design)

UML(Unified Model Language)统一建模语言

封装、继承、抽象、多态

封装(Encapsulation)

封装也叫作信息隐藏或者数据访问保护

类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据

比如说某个类的成员变量 不应该暴露出的set 或者 get 方法 使用具体有意义的函数,提供出操作成员变量的方式。同时保证成员变量之间的逻辑一致性。 比如说我们一个虚拟钱包代码如下:


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

那么封装的意义是什么?它能解决什么编程问题?

  1. 给类中的属性做访问限制,能够提高代码的可读性,可维护性。避免因为随意的操作导致属性逻辑上的不一致,比如说修改了账户余额,而没有修改账户的更新时间。
  2. 类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。比如给你一个非常多按键的遥控器,和给你一个只有必要按钮的遥控器。你一眼可以知道该如何操作,而且操作出错的概率也会降低很多。

抽象

封装主要讲的是如何隐藏信息,保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

在面向对象编程中,我们常借助编程语言提供的接口类,或者抽象类这两种语法机制,来实现抽象这一特性。 但是并不一定必须这种语法机制,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性。

比如图片存储的这一功能


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

当然也不是说需要接口和实现类这种才叫做抽象,通过函数包裹具体的实现逻辑,这本身就是一种抽象。 抽象这个概念是一个非常通用的设计思想,并不单单用在面向对象编程中,也可以来指导架构设计等。

抽象的意义是什么?它能解决什么编程问题?

  1. 抽象作为一种只关注功能点不关注实现的设计思路,帮助我们过滤点很多非必要信息。
  2. 抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。很多的设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程,开闭原则,代码解耦。
  3. 当然抽象思维指导我们不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改方法的定义。举个例子,比如getAliyunPictureUrl(),如果哪天我们不再是从阿里云上获取,而是从私有云上,这个命名也要随之被修改,相反我们定于一个比较抽象的函数,比如getPictureUrl(),那么即便修改了内部存储方式,我们也需要修改命名。

继承

继承用来表示类之间的is-a关系,提高了代码的复用能力。但是多继承可能导致代码的逻辑混乱,用好继承对编码能力提出了更高的要求,java语言不支持多继承。

继承存在的意义是什么?它能解决什么编程问题?

  1. 继承最大的好处就是代码复用。当然我们也可以通过其他方式来解决这个代码复用的问题,比如利用组合关系为不是继承关系。
  2. 从人类认知的角度上来说,继承是一种is-a关系,通过继承来关联两个类,反映真实世界中的这种关系,非常符合人类的认知,从设计角度来说,也是一种结构美感。
  3. 继承层次过深过复杂,就会导致代码可读性、可维护性变差。子类和父类高度耦合,修改父类代码会直接影响到子类。多用组合少用继承的设计思想

多态

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。 上代码:


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

多态这种特性也需要编程语言提供特殊的语法机制来实现。在上面的例子中,我们用到了三个语法机制来实现多态。

  • 第一个语法机制是编程语言要支持父类对象可以引用子类对象,也就是可以将 SortedDynamicArray 传递给 DynamicArray。
  • 第二个语法机制是编程语言要支持继承,也就是 SortedDynamicArray 继承了 DynamicArray,才能将 SortedDyamicArray 传递给 DynamicArray。
  • 第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法,也就是 SortedDyamicArray 重写了 DynamicArray 中的 add() 方法。 利用接口类来实现多态特性:

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

Java 这样的静态语言,通过继承实现多态特性,必须要求两个类之间有继承关系,通过接口实现多态特性,类必须实现对应的接口。

多态特性存在的意义是什么?它能解决什么编程问题?

  1. 多态特性能提高代码的可扩展性和复用性
  2. 多态是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式,基于接口而非实现编程,依赖倒置原则,里氏替换原则、利用多态去掉冗长的if-else语句等等。