理解设计模式之面向对象思考

面向对象欺我、骂我、笑我、侮我、辱我,我该如何处之?
只要读他、试他、理解他、吃透他、运用他,再过几年,你且看他?

面向对象是一种软件开发方法,分析问题时,以对象为单位,考虑它的属性及方法,与传统的面向过程是完全不同的,面向过程在分析问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
当每次把各种设计模式回归到面向对象思想时,我都有一种“运用之妙,存乎一心”的感觉。如果能更多的更深层次的去思考面向对象思想,对于学习设计模式将会大有裨益。
面向对象和面向过程的核心区别在于,它们是针对不同级别软件工程的。具体的实现问题依然还是需要面向过程,当处理一台复杂的“精密机器”时,使用面向对象则容易的多。当软件的复杂性进一步增长,可能面向对象也会吃力,我们就要考虑面向服务了。

目标

在80~90年代,大规模软件的复杂性增长到一定程度,如何构建一个高效、可靠的、可维护的复杂软件成了一个难题。面向对象思想在应付这次软件危机中起了很重要的作用,我们应该设计出一个如下系统:

  1. 可维护性好
  2. 可复用性好
  3. 可扩展性好
  4. 灵活性好

PS:第二次软件危机

从抽象说起

抽象虽然不是面向对象专利,但确是面向对象的核心。不仅如此,语言的发展其实也是抽象的发展,从低级语言到高级语言,从面向过程式到面向对象,从命令式到声明式都是抽象形态的演进。而面向对象就是为解决复杂问题提供了一种新的思维方式和抽象机制。

三大特征

封装

继承

多态

六大原则

设计原则乃是设计之本,很多人提出了各种各样的设计原则,从不同的角度去理解都是非常有道理的。这里只列举广为流传的六个原则,并稍作说明:

SRP:单一职责原则

OCP:开闭原则

LSP:里氏替换原则

DIP:依赖倒置原则

ISP:接口隔离眼则

LKP:迪米特原则

除了上述周知,《冒号课堂:编程范式与OOP思想》中总结了更多的设计原则,有些还有很强的操作性,非常具有指导意义,推荐学习。

更多思考

分离变化

分离变化是贯穿在整个面向对象设计中的核心思想,具有非常重要的指导意义。
封装内部变化,扩展外部变化。封装内部变化自不必说,通过继承和多态扩展外部变化,这就是面向对象的三大特征啊。面向对象思想很大程度上就是面向变化的思想,把各种各样的变化封装为对象,合理的分配这些对象,当然这里的合理是有技巧的。
软件设计应该基于不变的基石,分离变化的意义就在于此,帮助建立稳定模型。
分析变化需要一定的经验,if-else语句暗示着变化,扩展的业务暗示着变化…
转移变化需要一定的技巧,封装、依赖倒置、增加间接层…

继承与组合

面向对象系统功能复用的两大技术包括类继承和对象组合,二者选择有一句“格言”:优先使用对象组合,而不是类继承(Favor object composition over class inheritance)。这句话我很赞同,但我还想强调的是,二者的使用场景不同,并不能相互替换:类继承强调的是抽象复用(属于同一类)而对象组合强调的是实现复用(借用一下其他对象的行为实现)。而在实际项目中类继承常常被滥用于实现复用,我们应该消除此类过度继承,这才是我们强调组合优于继承的最主要原因。

绝大部分设计模式都基于继承或者组合实现,甚至有的设计模式比如适配器模式使用继承和组合都是正确的,分别为类适配器和对象适配器模式。而有的设计模式比如桥梁模式则是典型的同时使用了继承和组合,分别负责抽象部分和实现部分的复用。

抽象与扩展

有抽象的地方就有扩展,需要扩展的地方就需要抽象。
面向对象思想在这点上的支持非常好,假如现在有一个Object,抽象一下就变成了:

1
2
3
4
5
6
7
// interface or abstract class
public interface IAbstract {
}
public class ObjectA implements IAbstract {
}
public class ObjectB implements IAbstract {
}

这种结构我这边给取个名字”OU结构”,OU的意思是ObjectUnit,面向对象单元的意思,操作起来非常具体,后续文章中会大量用到这种方法。各个设计模式充斥着大量的这类代码,希望大家看到这样的一组结构,可以看成是“一个”类,一个包含着博大精深的面向对象思想的类,这会大大简化你对设计模式的理解。

从具体的接口与实现角度分析,我们应该面向接口编程而不是面向实现编程。接口本身是抽象,实现就是遵循这种抽象规则的具体扩展。在面向对象中,inteface或者abstract class巧妙的把抽象和扩展连接起来了,这很关键。

对象关系

当我们在讨论耦合的时候,很多时候是在讨论对象的关系。对象关系主要包括以下五种:继承、组合、聚合、关联、依赖。这些关系的耦合度依次递减(是不是体现了“组合优于继承”的思想)。
应该准确的使用这些对象关系,而避免使用不合适的对象关系。

设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

学习经典设计模式可以提高面向对象思维和编程实践能力,也更容易去理解其他一些框架,因为设计模式被广泛的应用在它们身上,又间接的加快了你的学习速度,非常有效。
23种设计模式是否囊括了所有问题的解决方案?显然这是不可能的,所以创建新的设计模式是完全可能,甚至是非常必要的。
已有的设计模式是经验也是桎梏,只要是在解决某些问题,经过反复验证是可行的模式,都可以考虑总结出新的设计模式。
事实上,现在有人提出了很多新的设计模式,比如NullObject模式、Gateway模式等等。

除了基本的设计模式,还有一些特别的设计模式:架构模式。我个人偏向区分开架构模式和设计模式,比如现在火热的架构模式有MVC、MVP、MVVM。它们往往比设计模式要复杂的多(其实Gof列出的23种设计模式仔细想一想,还是很简单的),考虑的工程化的东西非常多,往往要综合多个设计模式,甚至需要以完整的框架形式去支撑。正如郑晖老师所言:“设计模式是软件的战术思想,而架构是软件的战略决策”。

Anyway,我们的目标就是把设计模式训练成编程的条件反射。

元素模式

Jason McC.Smith大作《元素模式》提出了16种元素模式:创建对象、检索、继承、抽象接口、委托、重定向、集聚、递归、归复方法、扩展方法、委托型集聚、重定向型递归、信托型委托、信托型重定向、表亲型委托、表亲型重定向。

元素模式和设计模式一样,都是在制定软件系统中共通问题的解决方案。不过,元素模式认为设计模式还是化合物,应该有更基础的元素思想。

所有的面向对象的程序设计都可以仅使用四个事物来进行建模,它们分别是对象、方法、字段和类型。其中将方法、字段、和类型作为基本建模素材是很显然的,因为它们分别代表着程序设计的三个基本元素:功能、数据存储和数据描述。而对象又是面向对象程序的核心点和关键点,没有对象就没有面向对象程序设计。

元素模式,从侧面也告诉我们一个道理:模式是活的,不应死搬硬套。

敏捷开发

敏捷软件开发宣言:

人和交互    重于 过程和工具
可以工作的软件 重于 面面俱到的文档
客户合作    重于 合同谈判
随时应对变化  重于 遵循计划

虽然右项也有其价值,但我们认为左项更加重要。

当我们大谈特谈面向对象在解决复杂软件上的各种思想时,我们有没有矫枉过正?
不要迷失在过度架构,过度设计,过度抽象中… …
当然敏捷软件开发探索的是整个软件系统的开发方法论,不局限于编码设计阶段,这里只是提醒一下,小步伐,快迭代!

谈天说地

计算机科学领域的所有问题都可以通过增加一个间接中间层来解决。
当你改变关注点时事情就会发生变化。
思想出来了,只要一挤,代码就会往外喷起来。
程序和现实的冲突。
对象关联,解耦场景。
模式和简洁性的平衡。