《设计模式之美》二 面向对象(1)

《设计模式之美》二 面向对象(1)_第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); // 打印结果:1、3、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,在多继承时,判断方法、属性的调用路径
在当前类中找到方法,就直接执行,不再搜索;如果没有找到,就查找下一个类是否有对应方法,如果找到就直接执行,不再搜索;直到最后一个类,没找到就报错

你可能感兴趣的:(设计模式,java,oop)