设计模式之禅(一)
1 设计模式的起源
最早提出“设计模式”概念的是建筑设计大师亚力山大 Alexander。在 1970 年他的《建筑的永恒之道》里描述了投计模式的发现,因为它已经存在了千百年之久,而现代才被通过大量的研究而被发现。
在《建筑的永恒之道》里这样描述:“模式是一条由三个部分组成的通用规则:它表示了一个特定环境、一类问题和一个解决方案之间的关系。每一个模式描述了一个不断重复发生的问题,以及该问题解决方案的核心设计”。
2 软件领域设计模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
软件设计模式这个概念最早是在《设计模式 可复用面向对象软件的基础》一书中提出来的。他们(Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)所提出的设计模式主要是基于以下的面向对象设计原则:
对接口编程而不是对实现编程。
优先使用对象组合而不是继承。
正如该书副标题提到的两个要素:可复用和面向对象,前者是设计模式的目的和好处,后者则是软件设计的一个基础和手段。我们要理解和运用设计模式,我们应该从面向对象开始。反过来,理解设计模式之后,会对面向对象有更深的理解。
3 从面向对象谈起
3.1 软件设计固有的复杂性
"建筑商从来不会去想给一栋已建好的 100 层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。" ——Object-Oriented Analysis and Design with Applications
我认为软件设计复杂的根本原因在于:一个长时间维度里的各种不确定性和各种变化。
客户需求的变化
技术平台的变化
开发团队的变化
市场环境的变化
......
因此,一个程序员面对诸多变化时,要求他对软件领域和编程结构里变化的力量有深刻理解就显得尤为重要。我们在设计程序时如何去抵抗变化,这也是设计模式的一个核心所在。
3.2 如何解决复杂性
分解(面向过程)
通常做法:分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
抽象(面向对象)
从更高层次而言,处理复杂性有一个通用的手段,即抽象。由于不能掌握全部的复杂对象,我们往往会选择忽视它非本质的细节,而去处理泛化和理想化了的对象模型。
抽象属于更高层次的概念,在面对多变而又复杂的业务时,相比分而治之的做法更利于程序的稳定和复用。我这里要强调的是程序的复用性——这也是软件设计的金科玉律。
3.3 面向对象设计
面向对象三大特性
封装 隐藏内部实现细节;
继承 子类派生自父类,则子类拥有父类的方法和属性,便于资源的复用;
多态 改变行为。
面向对象设计
隔离变化 从宏观层面来看,面向对象相比面向过程的构建的方式更能抵抗变化,能将变化所带来的影响减为做小。
各司其职 从微观层面来看,面向对象的方式强调各个类的“责任”;由于需求变化导致的新增类型不应该影响原来类型的实现。
变化是复用的天敌,面向对象设计最大的优势在于:抵御变化。那么如何面向对象设计,则是我们开发者需要探讨的课题。
4 设计原则
好的面向对象设计并不是我们想怎么来就怎么来,我们还是需要建立在一套面向对象设计原则的基础上进行。它也是设计模式的基石,抛开设计原则来谈设计模式,这也是没有任何意义的。下面我会简单聊一下设计原则理论的部分,具体的应用在后面聊设计模式的时候讲解到。
4.1 单一职责原则(SR)
一个类应该只有一个引起变化的原因。
变化的方向隐含了类的责任。
4.2 依赖倒置原则(DIP)
高层模块(稳定)不应该依赖底层模块(变化)两者都应该依赖于抽象;
抽象不应该依赖细节;
细节应该依赖抽象。
本质:通过抽象层,使得各个类或模块之间的实现彼此独立, 实现模块间的解耦(间接依赖)。
例如订单模块中的 OrderController 和 OrderService 中引入一个抽象层——IOrderService。让 OrderController 依赖 IOrderService,而不再依赖其具体实现 OrderService,从而屏蔽掉 OrderService 中的具体实现修改时带来的变化。
4.3 里氏替换原则(LSP)
子类能够替换其基类被使用。
继承表达类型抽象。
4.4 开闭原则(OCP)
对扩展开放,对更改封闭。
类模块应该是可扩展的,但是不可修改。
封装变化、降低耦合,软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。因此,开放封闭原则主要体现在两个方面:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
4.5 接口隔离原则
一个接口尽量只服务于一个模块。
不应该迫使程序依赖它们不需要的方法(最小可用)。
4.6 迪米特法则
一个类知道的越少越好。
(其核心观念就是类间解耦,弱耦合。类于类之间牵扯越少越好,因为只有弱耦合复用率才高。)
4.7 组合优于继承
类继承通常为“白箱复用”,对象组合通常为黑“黑箱复用”。
继承在某种程度上破坏了封装性,子类父类耦合度高。
对象组合只要求被组合对象具有良好的接口定义,耦合度低。
4.8 封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界点一侧可以更改,而不对另一侧产生不良影响,从而实现松耦合。
4.8 面向接口编程
不将变化量抽象为一个类,而是声明成接口。
程序无需知道对象的具体类型,只需要知道对象所具有的接口。
减少系统中各部分的依赖关系,从而实现“高内聚,松耦合”。
评论