设计模式6大原则文档格式.docx
- 文档编号:374910
- 上传时间:2023-04-28
- 格式:DOCX
- 页数:34
- 大小:233.62KB
设计模式6大原则文档格式.docx
《设计模式6大原则文档格式.docx》由会员分享,可在线阅读,更多相关《设计模式6大原则文档格式.docx(34页珍藏版)》请在冰点文库上搜索。
6.在团队内部,最有效率也最有效果的信息传达方式,就是面对面的交谈。
7.可以工作的软件是进度主要的度量标准。
8.敏捷过程提倡可持续开发。
出资人、开发者和用户应该总是保持稳定的开发速度。
9.对卓越技术和良好设计的不断追求有助于提高敏捷性。
10.简单——尽量减少工作量的艺术是至关重要的。
11.最好的架构、需求和设计都源于自我组织的团队。
12.每隔一定时间,团队都要总结如何更右效率,然后相应地调整自己的行为。
极限编程实践
完整团队用户故事短交付周期验收测试结对编码测试驱动开发
集体所有权持续集成可持续的开发速度开放的工作空间计划游戏
简单设计重构隐喻
避免设计的臭味
∙僵化性(rigidity)——设计难以改变。
∙脆弱性(fragility)——设计易于遭到破坏。
∙顽固性(immobility)——设计难以重用。
∙粘滞性(viscosity)——难以做正确的事情。
∙不必要的复杂性(needlesscomplexity)——过分设计。
∙不必要的重复(needlessrepetition)——滥用鼠标进行复制、黏贴。
∙晦涩性(opacity)——混乱的表达。
设计原则
∙单一职责原则(SRP):
一个类应该只有一个发生变化的原因。
∙开放封闭原则(OCP):
软件实体应该对扩展开放,对修改关闭。
∙Liskov替换原则(LSP):
子类型(subtype)必须能够替换掉它的基类型(basetype)。
∙依赖倒置原则(DIP):
a.高层模块不应该依赖于低层模块。
二者都应该依赖于抽象。
b.抽象不应该依赖于细节。
细节应该依赖于抽象。
∙接口隔离原则(ISP):
不应该强迫客户程序依赖并未使用的方法。
∙DRY:
Don’trepeatyourselfPrinciple。
通过抽取公共部分放置在一个地方避免代码重复。
∙封装变化(Encapsulatewhatvaries)。
∙面向接口编程而不是实现(Codetoaninterfaceratherthantoanimplementation)。
∙优先使用组合而非继承(FavourCompositionOverInheritance)。
包和组件的设计原则
∙重用-发布等价原则(Reuse-ReleaseEquivalencePrinciple,REP):
重用的粒度就是发布的粒度。
∙共同重用原则(Common-ReusePrinciple,CRP):
一个组件中的所有类应该是共同重用的。
如果重用了组件中的一个类,那么就要重用组件中的所有类。
∙共同封闭原则(Common-ClosurePrinciple,CCP):
组件中的所有类对于同一种性质的变化应该是共同封闭的。
一个变化若对一个封闭的组件产生影响,则对该组件中的所有类产生影响,二对于其他组件则不造成任何影响。
∙无环依赖原则(Acycle-DependenciesPrinciple,ADP):
在组件的依赖关系图中不允许存在环。
∙稳定依赖原则(Stable-DependenciesPrinciple,SDP):
朝着稳定的方向进行依赖。
∙稳定抽象原则(Stable-AbstractionPrinciple,SAP):
组件的抽象程度应该与其稳定程度一致。
七种敏捷开发的方法敏捷开发包括一系列的方法,主流的有如下七种:
XP
XP(极限编程)的思想源自KentBeck和WardCunningham在软件项目中的合作经历。
XP注重的核心是沟通、简明、反馈和勇气。
因为知道计划永远赶不上变化,XP无需开发人员在软件开始初期做出很多的文档。
XP提倡测试先行,为了将以后出现bug的几率降到最低。
SCRUM
SCRUM是一种迭代的增量化过程,用于产品开发或工作管理。
它是一种可以集合各种开发实践的经验化过程框架。
SCRUM中发布产品的重要性高于一切。
该方法由KenSchwaber和JeffSutherland提出,旨在寻求充分发挥面向对象和构件技术的开发方法,是对迭代式面向对象方法的改进。
CrystalMethods
CrystalMethods(水晶方法族)由AlistairCockburn在20实际90年代末提出。
之所以是个系列,是因为他相信不同类型的项目需要不同的方法。
虽然水晶系列不如XP那样的产出效率,但会有更多的人能够接受并遵循它。
FDD
FDD(Feature-DrivenDevelopment,特性驱动开发)由PeterCoad、JeffdeLuca、EricLefebvre共同开发,是一套针对中小型软件开发项目的开发模式。
此外,FDD是一个模型驱动的快速迭代开发过程,它强调的是简化、实用、易于被开发团队接受,适用于需求经常变动的项目。
ASD
ASD(AdaptiveSoftwareDevelopment,自适应软件开发)由JimHighsmith在1999年正式提出。
ASD强调开发方法的适应性(Adaptive),这一思想来源于复杂系统的混沌理论。
ASD不象其他方法那样有很多具体的实践做法,它更侧重为ASD的重要性提供最根本的基础,并从更高的组织和管理层次来阐述开发方法为什么要具备适应性。
DSDM
DSDM(动态系统开发方法)是众多敏捷开发方法中的一种,它倡导以业务为核心,快速而有效地进行系统开发。
实践证明DSDM是成功的敏捷开发方法之一。
在英国,由于其在各种规模的软件组织中的成功,它已成为应用最为广泛的快速应用开发方法。
DSDM不但遵循了敏捷方法的原理,而且也适合那些成熟的传统开发方法有坚实基础的软件组织。
轻量型RUP
RUP其实是个过程的框架,它可以包容许多不同类型的过程,
CraigLarman极力主张以敏捷型方式来使用RUP。
他的观点是:
目前如此众多的努力以推进敏捷型方法,只不过是在接受能被视为RUP的主流OO开发方法而已。
单一职责原则(SingleResponsibilityPrinciple)
定义:
不要存在多于一个导致类变更的原因。
通俗的说,即一个类只负责一项职责。
问题由来:
类T负责两个不同的职责:
职责P1,职责P2。
当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方案:
遵循单一职责原则。
分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。
这样,当修改类T1时,不会使职责P2发生故障风险;
同理,当修改T2时,也不会使职责P1发生故障风险。
说到单一职责原则,很多人都会不屑一顾。
因为它太简单了。
稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。
在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。
而避免出现这一问题的方法便是遵循单一职责原则。
虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。
为什么会出现这种现象呢?
因为有职责扩散。
所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。
比如:
类T只负责一个职责P,这样设计是符合单一职责原则的。
后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。
但是在程序已经写好的情况下,这样做简直太费时间了。
所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。
举例说明,用一个类描述动物呼吸这个场景:
classAnimal{
publicvoidbreathe(Stringanimal){
System.out.println(animal+"
呼吸空气"
);
}
publicclassClient{
publicstaticvoidmain(String[]args){
Animalanimal=newAnimal();
animal.breathe("
牛"
羊"
猪"
运行结果:
牛呼吸空气
羊呼吸空气
猪呼吸空气
程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。
修改时如果遵循单一职责原则,需要将Animal类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:
classTerrestrial{
classAquatic{
呼吸水"
Terrestrialterrestrial=newTerrestrial();
terrestrial.breathe("
Aquaticaquatic=newAquatic();
aquatic.breathe("
鱼"
鱼呼吸水
我们会发现如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。
而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:
if("
.equals(animal)){
}else{
可以看到,这种修改方式要简单的多。
但是却存在着隐患:
有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛呼吸水”了。
这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。
还有一种修改方式:
publicvoidbreathe2(Stringanimal){
animal.breathe2("
可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。
这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?
其实这真的比较难说,需要根据实际情况来确定。
我的原则是:
只有逻辑足够简单,才可以在代码级别上违反单一职责原则;
只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;
例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。
实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。
遵循单一职责原的优点有:
<
!
--[if!
supportLists]-->
<
--[endif]-->
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
提高类的可读性,提高系统的可维护性;
变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都需要遵循这一重要原则。
里氏替换原则(LiskovSubstitutionPrinciple)
肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑。
其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(BarbaraLiskov)提出来的。
定义1:
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
定义2:
所有引用基类的地方必须能透明地使用其子类的对象。
有一功能P1,由类A完成。
现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。
新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
当使用继承时,遵循里氏替换原则。
类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
继承包含这样一层含义:
父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。
而里氏替换原则就是表达了这一层含义。
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。
比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。
举例说明继承的风险,我们需要完成一个两数相减的功能,由类A来负责。
classA{
publicintfunc1(inta,intb){
returna-b;
Aa=newA();
System.out.println("
100-50="
+a.func1(100,50));
100-80="
+a.func1(100,80));
100-50=50
100-80=20
后来,我们需要增加一个新的功能:
完成两数相加,然后再与100求和,由类B来负责。
即类B需要完成两个功能:
两数相减。
两数相加,然后再加100。
由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:
classBextendsA{
returna+b;
publicintfunc2(inta,intb){
returnfunc1(a,b)+100;
Bb=newB();
+b.func1(100,50));
+b.func1(100,80));
100+20+100="
+b.func2(100,20));
类B完成后,运行结果:
100-50=150
100-80=180
100+20+100=220
我们发现原本运行正常的相减功能发生了错误。
原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。
在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。
在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。
如果非要重写父类的方法,比较通用的做法是:
原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
里氏替换原则通俗的来讲就是:
子类可以扩展父类的功能,但不能改变父类原有的功能。
它包含以下4层含义:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。
所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?
后果就是:
你写的代码出问题的几率将会大大增加。
依赖倒置原则(DependenceInversionPrinciple)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;
抽象不应该依赖细节;
细节应该依赖抽象。
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。
这种场景下,类A一般是高层模块,负责复杂的业务逻辑;
类B和类C是低层模块,负责基本的原子操作;
假如修改类A,会给程序带来不必要的风险。
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
依赖倒置原则基于这样一个事实:
相对于细节的多变性,抽象的东西要稳定的多。
以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒置原则的中心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。
场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。
代码如下:
classBook{
publicStringgetContent(){
return"
很久很久以前有一个阿拉伯的故事……"
;
classMother{
publicvoidnarrate(Bookbook){
妈妈开始讲故事"
System.out.println(book.getContent());
Mothermother=newMother();
mother.narrate(newBook());
运行结果
妈妈开始讲故事
很久很久以前有一个阿拉伯的故事……
运行良好,假如有一天,需求变成这样:
不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事。
classNewspaper{
林书豪38+7领导尼克斯击败湖人……"
这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。
假如以后需求换成杂志呢?
换成网页呢?
还要不断地修改Mother,这显然不是好的设计。
原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。
我们引入一个抽象的接口IReader。
读物,只要是带字的都属于读物。
interfaceIReader{
publicStringgetContent();
Mother类与接口IReader发生依赖关系,而Book和Newsp
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 设计 模式 原则