一、如何理解面向对象的编程原则?
单一职责原则(Single Responsibility Principle)
- 一个类,应该由一组相关性很高的数据和方法组成。
- 一个类应该仅有一个引起它变化的原因。
单一职责最难界定的就是关于“职责”的定义,往往需要丰富的经验和对业务的认知程度,这也更加容易引起撕逼大战,似乎任何时候都可以拿它来辩论。如设计 ImageLoader 时,图片缓存功能和图片加载逻辑应该分开写在不同的类中。再比如在 Android 开发的早期 MVC 盛行,什么都往 Activity 里面加(网络请求、Adapter、Bean)导致 Activity 臃肿,这是典型的没有遵守单一职责造成的。
下面是违反单一职责原则的反例:
如果这样设计,随着需要实现的不同功能的类越来越多,这样的实现方式就会变成一种继承灾难。
- 出现棱形继承,是抽象程度不够的表现。
单一职责原则的关键词:
- 封装性
- 职责划分
- 细化抽象粒度
开闭原则(Open Closed Principle)
- 对扩展开放,对修改关闭。
当软件需要变化时,尽量通过扩展的方式来实现变化,而不是通过修改已有代码。通常情况下继承是最简单的方式,因为这种方式能保证已经正常 work 的代码不动,但扩展的方式不仅仅是通过继承,更多的是通过持有抽象来拥抱不同的变化。例如 ImageLoader 可以通过持有一个 ImageCache 的接口类型变量,在使用时需要提供不同缓存策略时,set 方法注入 ImageCache 接口的不同实现,达到不修改已有类的目的。
下面是违反开闭原则的例子:
正确的做法:
- 在面向对象语言里,提高代码可维护性的过程,大多数时候都是在消除条件分支语句(if & else )。
开闭原则的关键词:
- 可扩展性
- 抽象变化
- 可维护性
- 灵活性
- 稳定性
里氏代换原则(Liskov Substitution Principle )
- 任何基类出现的地方,子类也同样适用,是多态的体现。
- 里氏代换,是开闭原则的一种实现方案,也是大部分设计模式的基础。
- 只有满足里氏代换,子类能替换基类,才能够说明基类是真正能够被复用的。
比如 show(View) 方法传入不同的子类实现 TextView、 ImageView 等,就是使用 View 的不同子类来替换基类 View。再比如 Solider 类持有一把抽象的枪 Gun,使用时再传入具体的实现。因此一般持有抽象的类成员、方法参数都可以认为是里氏替换的例子。里氏替换的核心原理是抽象,且子类必须完全实现父类的方法,否则,应该用组合代替继承。
里氏替换的例子:
代理模式:
- 这里代理者和被代理者都实现了同样的接口,定义代理者时使用基类来引用。只有这样基类才是可复用的。
里氏代换原则的关键词:
- 可扩展性
- 抽象
- 可维护性
- 复用基类
- 开闭原则
依赖倒置原则(Dependence Inversion Principle)
- 依赖抽象接口,而非具体实现。
依赖倒置原则一句话就是:面向接口编程,或面向抽象编程。因为类与类之间一旦依赖于细节,就会产生耦合,例如 ImageLoader 中的缓存策略依赖于某一种具体的实现 MemoryCache 而不是 ImageCache 接口,这样需要更换缓存策略时就会不停的修改 ImageLoader 类。
- 比如,业务类中写了 18 层的继承关系,每一层都是实现类,还依赖了其他实现类,让人理不清关系。
依赖倒置原则的最大好处:降低耦合、隔绝变化
- 不同业务之间,不被彼此的变化影响;
- 明确开发、测试人员职责,提高协作效率;
- 故障隔离,单独业务的故障问题,不应该影响全局。
依赖倒置原则的关键词:
- 低耦合
- 隔绝变化
- 扩展性
- 灵活性