敏捷软件开发(2)--- 设计原则
之前讲设计模式系列的时候,也提过这些原则:
http://www.cnblogs.com/deman/category/634503.html
现在在根据敏捷一书,学习下。
我们已经有23种设计模式,是不是每一个类,功能都要用到设计模式?怎么选用合适的设计模式?
是不是开始开发了一个类,或者使用一个类以后,就不能修改这部分代码了吗?
其实每一次选择都是根据具体的情况而定,没有标准。
它依赖于设计者的经验,开发团队的能力以及协作度,时间周期,以及可以预见的扩展性。
它一定是一个基于,时间成本,人力成本,技术水平壁垒,以及产品开发周期等各种因素的一个综合结果。
在熟练掌握设计模式的基础上如何根据具体情况选择,这就是设计模式六大原则。
这些原则不是“某个人”提出来的,是根据几十年的软件开发项目的经验总结,是面向对象的“内功心法”。
敏捷讨论的是,怎么适应变化。
所有的设计模式讨论的都是拥抱变化,不是一种“万能型”的模式。
就像独孤九剑,没有固定的招式,但有明确的目的,“破招”。
敏捷开发的精髓就是,它依赖于人,视实际的情况而定,有时候不使用设计模式,也是一种模式!
1.单一职责原则
什么是职责?
职责就是变化的原因。也就是一个可以需要改动类的原因。
假设一个Modem的类,它有四个方法
package com.joyfulmath.agileexample.singletheroy; /** * @author deman.lu * @version on 2016-05-25 13:36 */ public interface Modem { public void dial(); public void handleup(); public void send(char c); public void recv(); }
这是一个Modem的类,它有2个职能,一个是连接,一个是数据收发。这2个职责一定要分开吗?
不一定,看具体情况。如果连接的部分进行修改,并不会引起数据部分的变化,这样send 和recv的代码需要从新编译,
然而他们并没有变化,这就是职责不单一的后果。但是如果这2块,会同时变化,如果把他们分开,就会有过度复杂化的问题。
还有一个推论,就是变化的轴线只有当变化实际应用的时候,才具有意义。
已这个例子为例,可以很好的阐释 敏捷到底是什么?
1)一开始开发Modem类的时候,连接和数据是绑定在一起的,我们并不需要分开他们。
所以当我们开发第一版的时候,我们并不知道后面会设计成什么样,可能会有那些变化,我们没有能力,也不应该去过度的考虑后续的扩展性,以及其他方面。
2)需求发生了变化,连接发生改变的时候,数据这块确没有任何变化。这时候我们就需要重构,需要把职责分开。
package com.joyfulmath.agileexample.singletheroy; /** * @author deman.lu * @version on 2016-05-25 14:16 */ public interface IConnect { void dial(String a); void handleup(int id); } package com.joyfulmath.agileexample.singletheroy; /** * @author deman.lu * @version on 2016-05-25 14:17 */ public interface IDataChannel { void send(char c); void recv(); } package com.joyfulmath.agileexample.singletheroy; /** * @author deman.lu * @version on 2016-05-25 14:17 */ public interface NewModem extends IConnect,IDataChannel{ }
通过接口的设计,连接和数据职能被分开了。
在这个需求变更的时候,我们对代码进行了重构,以满足单一原则,或者其他什么的。
这就是敏捷的精髓,我只在需要的时候进行下“小步修改”,而不是在某个节点,进行大规模的代码重构之类的。
敏捷发生在每一个时候,每一天,每一小时。敏捷只做必要的修改,过度的设计,也许永远不会发生。
2.开发封闭原则
对扩展开发,对更改封闭。
一个被经常用到的例子:
package com.joyfulmath.agileexample.oop; /** * @author deman.lu * @version on 2016-05-25 14:57 */ public abstract class Shape { abstract void draw(); } public class Cycle extends Shape { @Override void draw() { } } public class Supare extends Shape { @Override void draw() { } }
public class DrawShape { public void drawShapes(Vector<Shape> shapes) { for(Shape shape:shapes) { shape.draw(); } } }
我们可以添加一个类型,比如rectanle,但是上述的代码,我们都不不需要做任何修改,这就是对扩展开发,对修改封闭。
关键就是使用抽象的概念。
这是一个近乎完美的例子,但是就想一个厨师不可能作出满足所有人口味的美食一样,没有一个抽象或者其他技术可以解决所有的变化。
我们无法预测到需求的所有变化,只能是最可能的变化,这需要开发人员的经验。
but,如果要求所有的圆都比正方形先画。
这个变化,需要重构代码,已满足这个条件。
3.Liskov 替换原则
子类型必须能够替换掉它们的基本类型。
为什么有这么奇怪的名字,Liskov是一个人名,我们来看看这位计算机界的名人:
Barbara Liskov
上面是百度百科的介绍,具体你可以google一下她的经历。美国第一个获得计算机科学博士学位的女性(1968年,斯坦福大学)。
那时候知道计算机是什么东西的人,估计都不多。
这个原则表面上看和OCP是有些矛盾的。
我们先举例,然后在分析:
package com.joyfulmath.agileexample.lsp; /** * File Description: * * @auther deman * Created on 2016/5/29. */ public class Rectangle { private double width; private double height; Point topLeft; public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } }
这是一个矩形的定义,假设它运行良好,但是某天用户想要一个正方形。
正方形是一种特殊的矩形,把正方形从矩形中继承,符合一般的意义。
从上面的结构来说,正方形并不需要长和宽,它只要一条边的长度就可以。
如果我们不考虑内存的浪费,从Rectangle派生出Square 也会有问题。
如果把Square定义如下:
package com.joyfulmath.agileexample.lsp; /** * File Description: * * @auther deman * Created on 2016/5/29. */ public class Square extends Rectangle { @Override public void setWidth(double width) { super.setWidth(width); super.setHeight(width); } @Override public void setHeight(double height) { super.setWidth(height); super.setHeight(height); } }
这样看起来可以满足正方形的要求。
无论Square怎么设置,它都是正真意义上的正方形。而且Square是Rectangle 的子类。
但是问题来了,看如下的代码?
public class ShapeTest { public void g(Rectangle r) { r.setHeight(4); r.setWidth(5); if(r.getHeight()*r.getWidth() == 20) { } } }
当传入是正方形的时候,这个if条件就不会满足。这是一个不易发现的情况。就是正方形的行为与长方形不一致。
或者说这里需要区分正方形做特殊处理。这样就符合LSP,或者我们的常识。
所以我们发现正方形作为长方形的子类,这件事情是很能实现的,或者说会违反我们的常识!
从数学上来讲,正方形就是长方形的子类,但是从行为方式角度考虑,正方形和长方形的行为是严格区分的。
所以LSP所讲的一致性,就是指行为方式的一致。
如果某个基类的方法会抛出一个excption,而他的子类会抛出一个全新的excption,那么行为就是不一致,我们需要
对这个子类做特殊的处理,这就违反了LSP,也失去了多肽的意义。
要解决这个难题,就是把基类A和子类B公共的部分提取出来,成为 一个新的抽象类C,A & B 同时继承自C就可以解决不一致性。
现在来说说OCP & LSP的关联。
OCP所说的扩张,不是指对原有的子类 的方法赋予新的行为方式,而是创建新的派生类,对功能的扩展。
这样既满足OCP,也满足LSP。
如果原有的类结构无法满足OCP 或者LSP,甚至其他各原则。
敏捷就是,任何软件无法再任何时刻满足各种原则,和各种需求。
当需求改变时,我们就需要综合考虑各种成本,来实现和重构软件。
4.依赖倒置原则(DIP)
高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。
如图,PolicyLayer 的实现需要依赖于UtilityLayer,但是他们根本不打交道。所有这是很奇怪的设计。
PolicyLayer应该依赖于它的接口,也就是PolicyLayour是业务逻辑的模块。就像FM一项,我用那家公司的芯片都可以,
但是我需要明确我上层需要那些服务,然后由底层实现它。
如图 PolicyLayer只要由Policy Service Interface提高的服务就可以。至于Mechanism Layer,
如果通过PolicyServiceManager来管理,则PolicyLayer根本不需要知道Mechanism Layer。
换句话说,把Mechanism Layer换掉,不用修改PolicyLayer的任何一行代码,这样Mechanism Layer & PolicyLayer
就完全解耦。
根据依赖倒置规则我们可以推导3个小规则。
1)任何变量都不应该持有指向具体类的引用。
2)任何类都不应该从具体类派生
3)任何方法都不应该覆写它的基类中已经实现的方法。
这些规则应该在最复杂,不稳定的类或模块中使用。
因为如果你每个类都准寻这个方式,必将产生60%以上几本不变的类,缺写的过于复杂。
这就是代码过于复杂的味道。
你不能保证每个类都无比强大,应为这么做没有必要,而且效率底下。
就是一个国家的部队,不可能全是特种兵,国家也养不起这么多特种兵,只有合适的兵种搭配,才是强大的军队。
所以一个高效的设计,肯定是基于产品,开发团队,时间周期,产品质量等各方面因素综合的一个产物。
5.接口分离原则
先看一个Door的例子:
/** * @author deman.lu * @version on 2016-05-31 16:48 */ public interface Door { void lock(); void unlock(); boolean isDoorOpen(); }
/** * @author deman.lu * @version on 2016-05-31 16:57 */ public class Timer { public void Register(int timeOut,TimerClient client){ } }
/** * @author deman.lu * @version on 2016-05-31 16:58 */ public interface TimerClient { void TimeOut(); }
如果门长期开着,就发出alarm。所以我们需要一个TimerClient来发现TimeOut。如果一个对象希望获得time out的通知,那就让Time来注册这个对象。
我们看看如下的方案:
参考:
《敏捷软件开发》 Robert C. Martin
敏捷软件开发(2)--- 设计原则相关推荐
- 敏捷软件开发12条原则(译)
前段时间出了中文版的敏捷宣言和敏捷原则,于是来跟下风,按照自己的认识和理解,也来翻译下敏捷软件开发遵循的原则. 我们最优先做的工作是通过尽早地.持续地交付有价值的软件来使客户满意: 即使到了开发的后期 ...
- 敏捷软件开发宣言和原则
敏捷软件开发宣言(Manifesto for Agile Software Development) 我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人.由此我们建立了如下价值观:We ...
- 《敏捷软件开发(原则模式与实践)》读书笔记
<敏捷软件开发>读书分享 由于书是由英文书籍翻译,读起来会难免拗口,本次分享是由<敏捷软件开发>结合网上相关资料总结而成. 传统的瀑布式开发 瀑布模型式是最典型的预见性的方法, ...
- 敏捷软件开发的12个原则
作为一个软件工程师,软件设计和开发是最重要的技能,但是,从整个产品的角度上讲,项目管理能力比开发能力更重要,本文摘自Robert大叔的<敏捷软件开发>,粗体是Robert大叔的话,细体是我 ...
- 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则
第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理"胖"接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有"胖"接口 ...
- 《敏捷软件开发-原则、方法与实践》-Robert C. Martin
Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...
- 软件腐化的七个特征之牢固性和粘滞性(设计模式原则的反面) (《敏捷软件开发》读书总结第二篇)
文章目录 前言 牢固性(Immobility) 原文 我的理解 粘滞性(Viscosity) 原文 我的理解 前言 最近读Robert C. Martin(Bob大叔)的书<敏捷软件开发> ...
- 敏捷软件开发之原则篇
1.我们最优化先要做的是通过尽早的.持续的交付有减脂的软件来使客户满意. 2.即使到了开发的后期,也欢迎改变需求.敏捷过程利用变化来为客户创造竞争优势. 3.经常性地交付可以工作的软件,交付的间隔可以 ...
- 敏捷软件开发:原则、模式与实践(全)笔记
敏捷软件开发宣言 个体和交互 胜过 过程和工具 可以工作的软件 胜过 面面俱到 客户合作 胜过 合同谈判 响应变化 胜过 遵循计划 虽然右项也具有价值, 但我们认为左项具有更大的价值. 敏捷宣言遵循的 ...
最新文章
- 人算不如“云算”,且看新时代“借东风”
- 上帝的指纹——分形与混沌
- Sql字符串操作函数
- android 4.2修改设置菜单的背景颜色
- Linux中断不能进行任务调度,关中断是否禁止任务调度?关中断能作为互斥吗?...
- Java 集合中存在多种数据类型
- python怎么去掉换行符_python去除字符串中的换行符
- JS --- this(4)
- azure java_使用 Java 的 Azure 存储示例 | Microsoft Docs
- 微信投票作弊神器的制作代码
- 计算机等级考试一级wps office 教程,全国计算机等级考试一级WPSOffice教程
- badboy设置中文_录制脚本badboy工具使用手册
- JavaScript判断一个时间点在不在某段时间段范围之内
- Android外部存储设备管理——vold挂载大容量存储设备
- 条件运算符的嵌套_条件运算符
- BI工具和报表工具有什么不同
- laravel集合collect中的implode
- Python软件编程等级考试三级——20220618
- 5G 网络的会话性管理上下文对比介绍
- problem-1654B. Prefix Removals-codeforces