设计模式原则
不谈具体程序,谈的是你对软件的理解
模式:
每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。
“模式”这个词是不局限于软件开发行业的,它几乎无处不在,它其实就是一种经验的积累,就象大多数人的教育经历都是从小学到初中再到高中再到大学,这也是一种模式,是中国的教育模式;现在越来越火的出国热,也是另一种模式,海外留学模式
比设计模式更重要:GRASP (职责分配原则)
GRASP(General Responsibility Assignment Software Patterns),中文名称为“通用职责分配软件模式”,GRASP一共包括9种模式,它们描述了对象设计和职责分配的基本原则。也就是说,如何把现实世界的业务功能抽象成对象,如何决定一个系统有多少对象,每个对象都包括什么职责,GRASP模式给出了最基本的指导原则
可以说,GRASP是学习使用设计模式的基础。
1.Information Expert (信息专家)
信息专家模式是面向对象设计的最基本原则
如果某个类拥有完成某个职责所需要的所有信息,那么这个职责就应该分配给这个类来实现。这时,这个类就是相对于这个职责的信息专家。
例如:常见的网上商店里的购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品的数量即可。如下图:
问题
比较商品是否相同的方法需要放到那里类里来实现呢?
2. Creator (创造者)
实际应用中,符合下列任一条件的时候,都应该由类A来创建类B,这时A是B的创建者:
a. A是B的聚合
b. A是B的容器
c. A持有初始化B的信息(数据)
d. A记录B的实例
e. A频繁使用B
如果一个类创建了另一个类,那么这两个类之间就有了耦合,也可以说产生了依赖关系
我们能做的就是正确地创建耦合关系,不要随便建立类之间的依赖关系,
例如:因为订单(Order)是商品(SKU)的容器,所以应该由订单来创建商品。如下图:
3. Low coupling (低耦合)
低耦合模式的意思就是要我们尽可能地减少类之间的连接。
其作用非常重要:
a. 低耦合降低了因一个类的变化而影响其他类的范围。
b. 低耦合使类更容易理解,因为类会变得简单,更内聚。
关于低耦合,还有下面一些基本原则:
a. Don’t Talk to Strangers原则:
意思就是说,不需要通信的两个对象之间,不要进行无谓的连接,连接了就有可能产生问题,不连接就一了百了啦!
b. 如果A已经和B有连接,如果分配A的职责给B不合适的话(违反信息专家模式),那么就把B的职责分配给A。
c. 两个不同模块的内部类之间不能直接连接,
例如:Creator模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。如下图:
4. High cohesion (高内聚)
高内聚的意思是给类尽量分配内聚的职责,也可以说成是功能性内聚的职责。即功能性紧密相关的职责应该放在一个类里,并共同完成有限的功能,那么就是高内聚合。这样更有利于类的理解和重用,也便于类的维护。
例如:一个订单数据存取类(OrderDAO),订单即可以保存为Excel模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计,如下图:
5.Polymorphism (多态)
这里的多态跟OO三大基本特征之一的“多态”是一个意思。
例如:我们想设计一个绘图程序,要支持可以画不同类型的图形,我们定义一个抽象类Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape类里的Draw()方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形,如下图:
6.Pure Fabrication (纯虚构)
这里的纯虚构跟我们常说的纯虚构函数意思相近。高内聚低耦合,是系统设计的终极目标,但是内聚和耦合永远都是矛盾对立的。高内聚以为这拆分出更多数量的类,但是对象之间需要协作来完成任务,这又造成了高耦合,反过来毅然。该如何解决这个矛盾呢,这个时候就需要纯虚构模式,由一个纯虚构的类来协调内聚和耦合,可以在一定程度上解决上述问题。
例如:上面多态模式的例子,如果我们的绘图程序需要支持不同的系统,那么因为不同系统的API结构不同,绘图功能也需要不同的实现方式,那么该如何设计更合适呢?如下图:
7.Indirection (间接)
“间接”顾名思义,就是这个事不能直接来办,需要绕个弯才行。绕个弯的好处就是,本来直接会连接在一起的对象彼此隔离开了,一个的变动不会影响另一个。就想我在前面的低耦合模式里说的一样,“两个不同模块的内部类之间不能直接连接”,但是我们可以通过中间类来间接连接两个不同的模块,这样对于这两个模块来说,他们之间仍然是没有耦合/依赖关系的。
8. Controller (控制器)
用来接收和处理系统事件的职责,一般应该分配给一个能够代表整个系统的类,这样的类通常被命名为“XX处理器”、“XX协调器”或者“XX会话”。
关于控制器类,有如下原则:
a. 系统事件的接收与处理通常由一个高级类来代替。
b. 一个子系统会有很多控制器类,分别处理不同的事
9. Protected Variations (受保护变化)
预先找出不稳定的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过接口扩展新的功能,而不需要去修改原来旧的实现。也可以把这个模式理解为OCP(开闭原则)原则,就是说一个软件实体应当对扩展开发,对修改关闭。在设计一个模块的时候,要保证这个模块可以在不需要被修改的前提下可以得到扩展。这样做的好处就是通过扩展给系统提供了新的职责,以满足新的需求,同时又没有改变系统原来的功能
几位名人的话:
1.人与人之间的交互是复杂的,并且其效果从来难以预期,但却是工作中最为重要的方面
----Tom DeMacro和Timothly Lister
==团队力量
2.教堂尖端上的风标,即使由钢铁制成,如果不懂顺应风势的艺术,一样会被暴风立即摧毁。
-----海因里希.海捏
==实践的力量
敏捷软件开发宣言
我们正在通过亲身实践以及帮助他人实践,揭示更好的软件开发方法。通过这项工作,我们认为:
个体和交互 胜过 过程和工具
可以工作的软件 胜过 面面具到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划
虽然右项也具有价值,但是我们认为左项具有更大的价值
个人理解和认为需要添加的
个人理解:
1. 一个团队里的成员需要相互开源,相互交流,相互探讨,不过不是在自己没有独立思考的讨论,这样的工作环境要比你用好的中间件,好的开发工具更有成效。
2. 针对客户用不同的开发模式,一个可以工作的软件表达的意思要比任何文明了,简洁,没有二义性。但不认为文档不重要,有些文档必要,数据字典,进度报表,人员安排,类关系图等
3. 关于客户方面接触不是很多
4. 变化是设计模式的产生的原因,是面向对象产生的根本
添加点:
通俗易懂的例子 胜过 抽象的算法
即:用形象的例子,用生活中的事物去解释程序的算法
12条原则
1. 我们最优先要做的是通过尽早的,持续的交付有价值的软件来使客户满意
2. 即使到了开发后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
3. 经常性地交付可以工作的软件,交付的间隔可以从几周到几个月,交付时间间隔越短越好。
4. 在整个项目开发期间,业务人员和开发人员必须天天一起工作
5. 围绕被激励起来的个人来构建项目。给他们提供需要的环境和支持,并且信任他们能够完成工作
6. 在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面的交谈。
7. 工作的软件是首要的进度度量标准
8. 敏捷过程提倡可持续的开发速度。责任人,开发责和用户应该能够保持长期的,恒定的开发速度。
9. 不断的关注优秀的技能和好的设计会增强敏捷能力
10. 简单—使未完成的工作量最大化艺术---是根本的
11. 最好的构架,需求和设计出自于自组织的团队。
12. 每隔一定时间,团队会在如何才能更有效地工作进行反省,然后相应地对自己的行为进行调整。
面向对象基本原则
单一职责原则(SRP)
就一个类而言,应该仅有一个引发他们变化的原因
开放—封闭原则(OCP)
“软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改
依赖倒置原则(DIP)
抽象不应该依赖于细节。细节应该依赖于抽象。”关于这个原则,还有种说法是.“高层不应该依赖于底层,两者都应该依赖于抽象。”其实怎么说都是对的,关键就是要理解一点,只有抽象的东西才是最稳定的,也就是说,我们依赖的是它的稳定。
接口隔离原则(ISP)
“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
面向对象原则分析:
SRP:例子
一般做法:
SRP做法:
OCP:
DIP:
例如:参考下图的设计,一个开关跟灯直接连接在一起了,也就是说开关依赖于灯的打开和关闭方法,那么如果我想用这个开关也可以打开其他东西呢,比如电视、音响。显然这个设计是无法满足这个要了,因为我们依赖了细节而不是抽象,这个开关已经等价于“灯的开关”
DIP:
ISP:
ISP:
个人认为也可以用抽象类
职责:
变化的原因
1. 考虑能够工作的最简单事情:使用简单的方案完成需求
EJB(企业级Java Bean) ,socket,ORB(对象请求代理)RMI(远程方法调用)
2. 你将不需要它
3. 一次,并且只有一次:
不能容忍重复代码。无论在哪里发现重复代码,都要消灭。
消除重复的方法是抽象
SRP,单一职责原则:就一个类而言,应该仅有一个引起它变化的原因
· 将过多的职责耦合在一个类中导致了脆弱设计
· 职责是变化的原因
· 如果应用程序变化的方式总是导致两个职责同时变化,则不应该分离他们
· 把业务规则和持久化子系统绑定在一起是自讨苦吃,这违反了单一职责原则。可以使用 Facade 或者 Proxy 模式进行重构,以解除这种耦合
· 软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离
OCP,开放封闭原则:软件实体(类、模块、函数等等)应该是可扩展的,但是不可修改的
· 任何系统在其生命周期内都回变化
· 变化应该是导致添加新的代码,而不是修改已经正常运行的代码
· OCP的关键是抽象
· 接口和客户的关系要比实现它的类和客户的关系更密切
· 策略模式和模板模式是用以满足OCP的最常用方法
· 没有对于所有情况都贴切的模型,也即不可能完全封闭,所以应该预先猜测出最有可能的变化种类
· 拒绝不成熟的抽象和抽象本身一样重要
LSP,LISKOV可替换原则:子类型必须能够替换它们的基类型
· 对于LSP的违反也潜在的违反了OCP
· 正方形从长方形继承的例子微妙的违反了这种原则
· 在考虑一个特定的设计是否恰当时,不能孤立地来看这个解决方案,必须根据设计的使用者所做出的合理假设来审视它
· 对象的行为方式是软件真正所关注的问题, IS A 关系是就行为而言的
· 派生类只能使用相等或更弱的前置条件来替换原始的前置条件,只能使用相等或更强的前置条件来替换原始的后置条件。
· 完成的功能少于基类的派生类通常不能替换基类,也不符合LSP
· 派生类不应该抛出基类不能预计的异常,否则违反LSP
DIP,依赖倒置原则:高层模块不应该依赖于底层模块,二者都应该依赖于抽象
· 抽象不应该依赖于细节,细节应该依赖于抽象
· 所有结构良好的面向对象架构都具有清晰的层次定义,每个层次通过一个定义良好的受控接口向外提供一组内聚的服务
· 一个对象持有另外一个具体对象的引用可能破坏了DIP
· 策略不应该依赖于细节,细节和策略都应该依赖于抽象
· 如果依赖关系是倒置的,那么就是面向对象的设计,否则就是过程化的设计。
ISP,接口隔离原则:不应该强迫客户依赖于他们不用的方法,将不同的接口分离,而不是集合
· 如果强迫客户程序依赖于那些他们不使用的方法,那么这些客户程序就面临着由于这些未使用方法的改变而带来的变更。
· 使用委托分离接口
· 使用多重集成分离接口