之前讲设计模式系列的时候,也提过这些原则:

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来注册这个对象。

我们看看如下的方案:

这样TimerDoor 就可以注册到Timer上面。但是这样写有个问题,Door其实跟TimerClient没有关系。
这就是接口胖的味道。
如何分离TimeClient & Door ,可以有很多选择,这里我们使用多重继承的方式。

参考:

《敏捷软件开发》 Robert C. Martin

敏捷软件开发(2)--- 设计原则相关推荐

  1. 敏捷软件开发12条原则(译)

    前段时间出了中文版的敏捷宣言和敏捷原则,于是来跟下风,按照自己的认识和理解,也来翻译下敏捷软件开发遵循的原则. 我们最优先做的工作是通过尽早地.持续地交付有价值的软件来使客户满意: 即使到了开发的后期 ...

  2. 敏捷软件开发宣言和原则

    敏捷软件开发宣言(Manifesto for Agile Software Development) 我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人.由此我们建立了如下价值观:We ...

  3. 《敏捷软件开发(原则模式与实践)》读书笔记

    <敏捷软件开发>读书分享 由于书是由英文书籍翻译,读起来会难免拗口,本次分享是由<敏捷软件开发>结合网上相关资料总结而成. 传统的瀑布式开发 瀑布模型式是最典型的预见性的方法, ...

  4. 敏捷软件开发的12个原则

    作为一个软件工程师,软件设计和开发是最重要的技能,但是,从整个产品的角度上讲,项目管理能力比开发能力更重要,本文摘自Robert大叔的<敏捷软件开发>,粗体是Robert大叔的话,细体是我 ...

  5. 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则

    第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理"胖"接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有"胖"接口 ...

  6. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  7. 软件腐化的七个特征之牢固性和粘滞性(设计模式原则的反面) (《敏捷软件开发》读书总结第二篇)

    文章目录 前言 牢固性(Immobility) 原文 我的理解 粘滞性(Viscosity) 原文 我的理解 前言 最近读Robert C. Martin(Bob大叔)的书<敏捷软件开发> ...

  8. 敏捷软件开发之原则篇

    1.我们最优化先要做的是通过尽早的.持续的交付有减脂的软件来使客户满意. 2.即使到了开发的后期,也欢迎改变需求.敏捷过程利用变化来为客户创造竞争优势. 3.经常性地交付可以工作的软件,交付的间隔可以 ...

  9. 敏捷软件开发:原则、模式与实践(全)笔记

    敏捷软件开发宣言 个体和交互 胜过 过程和工具 可以工作的软件 胜过 面面俱到 客户合作 胜过 合同谈判 响应变化 胜过 遵循计划 虽然右项也具有价值, 但我们认为左项具有更大的价值. 敏捷宣言遵循的 ...

最新文章

  1. 人算不如“云算”,且看新时代“借东风”
  2. 上帝的指纹——分形与混沌
  3. Sql字符串操作函数
  4. android 4.2修改设置菜单的背景颜色
  5. Linux中断不能进行任务调度,关中断是否禁止任务调度?关中断能作为互斥吗?...
  6. Java 集合中存在多种数据类型
  7. python怎么去掉换行符_python去除字符串中的换行符
  8. JS --- this(4)
  9. azure java_使用 Java 的 Azure 存储示例 | Microsoft Docs
  10. 微信投票作弊神器的制作代码
  11. 计算机等级考试一级wps office 教程,全国计算机等级考试一级WPSOffice教程
  12. badboy设置中文_录制脚本badboy工具使用手册
  13. JavaScript判断一个时间点在不在某段时间段范围之内
  14. Android外部存储设备管理——vold挂载大容量存储设备
  15. 条件运算符的嵌套_条件运算符
  16. BI工具和报表工具有什么不同
  17. laravel集合collect中的implode
  18. Python软件编程等级考试三级——20220618
  19. 5G 网络的会话性管理上下文对比介绍
  20. problem-1654B. Prefix Removals-codeforces

热门文章

  1. 台湾半导体制造商台积电市值首次超越英特尔
  2. tree, RB-tree(红黑树)
  3. 抖音3D旋转相册 (源码下载)
  4. 国家鼓励的软件企业定期减免企业所得税
  5. dedecms 对不起,你输入的参数有误修改
  6. powermill2020错误代码1603_max2020安装提示1603
  7. 用C语言实现电脑小游戏——扫雷
  8. excel中的CTRL+E格式选取
  9. pstack 安装linux_linux下的进程堆栈查看工具pstack
  10. QPE(量子相位估计)