
设计原则与思想:面向对象(11讲)
1. 什么是面向对象编程(OOP)
- 面向对象编程是一种
编程范式
或编程风格
。它以类或对象
做为组织代码的基本单元,并将封装、抽象、继承、多态四个特性
,做为代码设计和实现的基石
2. 什么是面向对象编程语言
- 面向对象编程语言是
支持类或对象的语法机制
,并有现成的语法机制,能方便地实现面向对象编程四大特性
(封装、抽象、继承、多态)的编程语言
3. 面向对象编程和面向对象编程语言之间的关系
- 面向对象编程通常使用面向对象编程语言来进行,可是,不用面向对象编程语言,咱们照样能够进行面向对象编程。反过来说,即使咱们使用面向对象编程语言,写出来的代码也不必定是面向对象编程风格的,也有多是面向过程编程风格的
4. UML 统一建模语言
5. 封装、继承、多态、抽象分别解决哪些编程问题
* 封装
- 概念:封装也叫做
信息隐藏
或者数据访问保护
,类
经过暴露有限的访问接口
,受权外部仅能
经过类提供的方法(函数)
来访问内部的信息和数据
- 做用:
- 保护数据不被随意修改,提升代码的
可维护性
- 仅暴露有限的必要接口,提升
类的易用性
- 封装特性,必须使用
访问控制权限
- 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();
}
}
- 解析
-
- 从业务的角度来讲,id、createTime 在建立钱包的时候就肯定好了,以后不该该再被改动,因此,咱们并无在 Wallet 类中,暴露 id、createTime 这两个属性的任何修改方法,好比 set 方法。因此,
在 Wallet 类的构造函数内部将其初始化设置好
,而不是经过构造函数的参数来外部赋值。
-
- 对于钱包余额 balance 这个属性,从业务的角度来讲,只能增或者减,不会被从新设置。因此,咱们在 Wallet 类中,只暴露了 increaseBalance() 和 decreaseBalance() 方法,并无暴露 set 方法。对于 balanceLastModifiedTime 这个属性,它彻底是跟 balance 这个属性的修改操做绑定在一块儿的。只有在 balance 修改的时候,这个属性才会被修改。因此,咱们把 balanceLastModifiedTime 这个属性的修改操做彻底
封装
在了increaseBalance() 和 decreaseBalance() 两个方法中,不对外暴露任何修改这个属性的方法
和业务细节。这样也能够保证 balance 和 balanceLastModifiedTime 两个数据的一致性。
抽象
- 概念: 抽象是如何隐藏方法的具体实现,让使用者只须要关心调用哪些方法,不须要知道具体如何实现
- 抽象能够经过
接口类
(Java中的interface关键字) 或 抽象类
(Java中的abstract关键字)来实现
- 做用:
- 提升代码的可扩展性、维护性,修改时不须要改变定义,减小代码的改动范围
- 抽象是处理复杂系统的有效手段,能有效过滤掉没必要要关注的信息
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) { ... }
}
- 解析
-
- 在上面的这段代码中,咱们利用 Java 中的 interface 接口语法来实现抽象特性
-
- 调用者在使用图片存储功能的时候,只须要了解 IPictureStorage 这个接口类暴露了哪些方法就能够了,不须要去查看 PictureStorage 类里的具体实现逻辑
-
- 举个简单例子,好比 getAliyunPictureUrl() 就不是一个具备抽象思惟的命名,若是更换存储设备,那这个命名也要随之被修改。相反,若是咱们定义一个比较抽象的函数,好比叫做 getPictureUrl(),那即使内部存储方式修改了,咱们也不须要修改命名。
继承
- 概念:继承用来表示
类与类之间is-a关系
,可分为单继承和多继承
- 实现继承,须要语言的语法支持,Java 使用
extends
关键字来实现继承,C++ 使用冒号
(class B : public A),Python 使用 paraentheses()
,Ruby 使用 <
- 单继承:Java、PHP、C#、Ruby 等
- 多继承:C++、Python、Perl 等
- 做用: 提升
代码复用性
,好比通用的方法抽象到父类
- 缺点: 过分使用继承,继承层次过深、过复杂,就会致使代码的可读性、可维护性变差
- 解决思路:
多用组合少用继承
多态
- 概念:
父类,被多个子类继承,若是父类的某个方法,在多个子类中被重写,表现出不一样功能,就是多态(同一个类的不一样子类表现不一样形态)
- 实现方法:
-
-
继承+方法重写
-Demo1
-
- 利用
接口类
语法(C++不支持) -Demo2
-
- 利用
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采用MRO
,在多继承时,判断方法、属性的调用路径 在当前类中找到方法,就直接执行,再也不搜索;若是没有找到,就查找下一个类是否有对应方法,若是找到就直接执行,再也不搜索;直到最后一个类,没找到就报错