文章目录

  • 单一职责原则(SRP)
  • 开放-封闭原则(OCP)
  • Liskov 替换原则(LSP)
  • 接口隔离原则(ISP)
  • 依赖倒置原则(DIP)

设计中的拙劣设计症状:

  • 僵硬性(Rigidity):很难对系统进行改动,因为每个改动会迫使很多对系统其他部分的其他改动。
  • 脆弱性(Fragility): 对系统的改动会导致系统中和改动地方在概念上无关的许多地方出现问题。
  • 牢固性(Immobility): 很难解开系统的纠结,让其成为在其他系统中重用的组件
  • 粘滞性(Viscosity):做正确的事情比做错误的事情困难
  • 不必要的复杂性(Needless Complexity):设计中包含不具有任何直接好处的设计
  • 不必要的重复(Needless Repetition): 设计中包含重复结构,而重复的结构本可以使用单一的抽象进行统一
  • 晦涩性(Opacity):很难阅读、理解,没有很好地表现出意图。

相应的为了防止这些不好的设计,有如下原则需要遵守,即 SOLID

  • 单一职责原则(The Single Responsibility Principle, SRP)
  • 开放封闭原则(The Open-Close Principle, OCP)
  • Liskov 替换原则(The Liskov Substitution Principle, LSP)
  • 接口隔离原则(The Interface Segregation Principle, ISP)
  • 依赖倒置原则(The Dependency Inversion Principle, DIP)

我们不能因为设计的退化而责备需求的变化,作为开发人员大多数人都意识到需求是项目中最不稳定的要素,如果我们的设计由于持续、大量的需求变化而失败,那就表明是我们的设计和实现本身是有缺陷的。

书中举了一个很有代表性的例子,你的老板需要你编写一个从键盘输入字符并输出到打印机的程序。正常的设计就是一下三块:

Read KeyBoard -> Copy -> Write Printer

于是写下如下的实现:

void Copy() {int c;while((c=RdKbd())!=EOF)WrtPrt(c);
}

这里运行 OK,开始应用到系统中,几个月后,老板说希望 Copy 程序能从纸带读入信息,这时你的修改方案可能是在 Copy 中添加一个 bool 变量,值为 true 就从纸带读,false 就从键盘读,但是现在改接口不太现实,不用该接口的方式只能增加一个全局变量,然后使用 ? : 操作符。

bool ptFlag = false;
// remeber to reset this flag
void Copy() {int c;while((c=( ptFlag ? Rdpt() : RdKbd())) != EOF)WrtPrt(c);
}

这样项使用纸带读入必须 ptFlag 是 true,并且函数返回以后,还需要重置 ptFlag 成默认的 false,因此你还贴心的加了一行注释来提醒。

几周之后你的老板告诉你,客户有的时候会虚妄 Copy 程序可以输出到纸带穿孔机上。这次的修改跟上次相似,只不过需要另外的一个全局变量和 ? : 操作符。

bool ptFlag = false;
bool punchFlag = false
// remeber to reset these flags
void Copy() {int c;while((c=( ptFlag ? Rdpt() : RdKbd())) != EOF)punchFlag ? WrtPunch(c) : WrtPrt(c);
}

可以看到经过两次需求的变更,上面的代码就表现出了拙劣设计的特征,每次输入设备的变更,都要对while 的循环条件判断进行彻底的重新组织。

在使用敏捷开发方法,初始实现都是相同的简单,但在老板第一次从纸带读入机中读取信息时,他们就会做出下面的反应,修改设计并使修改后的设计对于那一类需求的变化具有弹性。

class Reader {public:virtual int read() = 0;
};class KeyboardReader : public Reader {public:virtual int read() { return RdKbd(); }
}KeyboardReader GdefaultReader;
void Copy(reader& reader = GdefaultReader) {int c;while((c=reader.read())!=EOF)WrtPrt(c);
}

这样在实现新需求时,抓住这次机会去改进设计,以便设计对于将来的同类变化具有弹性,而不是设计去给设计打补丁,以上的代码,不论老板要求一种新的输入设备,团队都可以能以不导致 Copy 程序退化的方式做出反应。以上是遵循了开放-封闭原则。

敏捷开发人员知道要做什么,是因为:

(1)他们遵循敏捷实践去发现问题;
(2)他们应用设计原则去诊断问题;并且
(3)他们应用适当的设计模式去解决问题。

单一职责原则(SRP)

内聚性(cohesion):一个模块的组成元素之间的功能相关性

对一个类而言,应该仅有一个引起它变化的原因。

当一个类承担的职责过多,就等于把这些职责耦合在了一起,会导致代码脆弱。

例如下面的 case :

class Modem {public:void dial(String pno);void hangup();void send(char c);void recv();
}

上面的类就有两个职责,第一个是负责连接管理, dial 和 hangup 函数进行调制解调器的连接处理,send 和 recv 则进行数据通信。可以使用一些设计模式进行重构。

开放-封闭原则(OCP)

软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改的。

主要是此模式对于扩展是开放的,对于更改是封闭的。

这个准则的关键就是抽象。模块可以操作一个抽象体,其依赖一个固定的抽象体,它对于更改是可以关闭的,同时通过抽象的派生,对扩展是开放的。

示例 demo:

enum ShapeType{ circle, square};struct Shape {ShapeType itsType;
};struct Circle {ShapeType itsType;double itsRadius;Point itsCenter;
};struct Square {ShapeType itsType;double itsSide;Point itsTopLeft;
};typedef struct Shape *ShapePointer;void DrawAllShape(ShapePointer list[], int n) {int i;for(i = 0; i<n;i++) {struct Shape* s = list[i];switch (s->itsType) {case square: DrawSquare((struct Square*)s); break;case circle: DrawCircle((struct Circle*)s); break;}}
}

上面的代码就不符合开放封闭原则,对于新的形状类型的添加不是封闭的,如果继续添加一个三角形绘制,就必须要改这个函数。并且也需要修改 ShapeType enum,因为所有形状都依赖这个 enum 声明,一旦改动,所有形状模块都需要重新编译。并且想在另一个程序中复用 DrawAllShape 还必须带上 Shape 类和 Circle 类,即使那个新程序不需要它们。

遵循 OCP 的改动如下:

class Shape {public:virtual void Draw() const = 0;
}class Square : public Shape {public:virtual void Draw() const;
}class Circle : public Shape {public:virtual coid Draw() const;
}void DrawAllShape(vector<Shape*> &list){vector<Shape*>::iterator I;for(auto i=list.begin();i!=list.end();++i)(*i)->Draw();
}

以上是对于类型的变化封闭了,但是现在变化要求所有圆必须在方块之前绘制,那么这里 DrawAllShape 就没办法对变化封闭了,所以无论模块有多”封闭“,都会存在一些无法封闭的变化,因此这个就要设计人员对他设计的模块哪个变化封闭做出选择了。

Liskov 替换原则(LSP)

子类型必须能够代替掉它们的基类型。

如果违反了 LSP,往往也会潜在地违反了 OCP。

接口隔离原则(ISP)

不应该强迫客户依赖于它们不用的方法。

即现在有基类A和子类B和子类C,现在因为需求变化, B 中需要增加一个接口,为了给子类B用,就要在基类A 中加入这个新方法,同时这个新方法也会被引入到 C 中,这样随着子类的需求要不断被加入到基类的接口,使接原来越臃肿。

这个时候我们就应该使用委托或者多重继承来分离接口。

依赖倒置原则(DIP)

高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

假如现在有一个简答的系统,一个 Button对象通过Poll消息来获取是否被按下,Lamp 对象则接收到 turn on 的消息就开灯,turn off的消息就关灯。如何设置一个用 Button 来控制 Lamp 对象的系统呢?

class Button {public:void poll(){if(/*some condition*/)itsLamp.turnOn();}private:Lamp itsLamp;
}

上面是 Button 类直接依赖了 Lamp 类,这个依赖关系表示当 Lamp 类改变时,Button 类会收到影响,并且要通过 Button 来控制一个 Motor 对象是不可能的。上面的方案就违反了 DIP ,应用程序的高层策略没有个底层实现分离,高层策略就自动地依赖了低层模块,抽象就自动地依赖了具体细节。

高层策略,即应用背后的抽象,是那些不随具体细节变化而变化的真理。本例中背后的抽象是检测用户的开/关指令并将指令传给目标对象,用什么检测用户指令,传给的目标对象是什么都不重要。

敏捷开发 SOLID 原则相关推荐

  1. 敏捷开发详解(含义、原则、目标、机制)

    我们理解东西习惯从已知连接未知,首先我们来对比一下.我们最先了解到的是瀑布模型,那么它就是不敏捷的.瀑布开发模式把开发分成一系列阶段,如需求.设计.开发.测试,就像下图它画出来的,看起来很像瀑布,所以 ...

  2. 敏捷开发流程的8个步骤_敏捷开发——个体和互动高于流程和工具

    敏捷开发是软件公司主流的项目管理方法,敏捷方法论有许多种,包括Scrum.极限编程(XP)以及精益(Lean)方法,但是它们都具有一个共同点:遵循敏捷宣言和敏捷原则. 透明性.每一个敏捷项目成员都知道 ...

  3. 敏捷开发,持续集成/交付/部署, DevOps总结

    文章目录 敏捷开发入门教程 一.迭代开发 二.增量开发 三.敏捷开发的好处 3.1 早期交付 3.2 降低风险 四.如何进行每一次迭代 五.敏捷开发的价值观 六.十二条原则 七.参考链接 持续集成/交 ...

  4. 敏捷过程(小规模团队敏捷开发)

    敏捷过程(小规模团队敏捷开发) 一.敏捷过程 1.1 敏捷开发的价值观 1.2 敏捷开发应遵循的12条原则 二.敏捷开发的原则 2.1 敏捷团队运作机制 2.2 关键的团队角色 2.3 产品负责人(P ...

  5. 开发模型的理解:瀑布模型/增量式/迭代/敏捷开发——笔记

    首先,不管采用何种开发模型.软件开发都至少具有以下的周期,包括: 需求获取/分析(系统分析.软件分析) 设计 实现 测试 发布(运行) 维护 正在上传-重新上传取消 既然所有的开发模型都具有相同的开发 ...

  6. 【软件测试基础理论知识】1.3软件开发模型之—敏捷开发(敏捷模型)

    敏捷开发 一. 什么是敏捷开发 1.1 敏捷开发的定义 1.2 敏捷开发的原则 1.3 敏捷开发的特点 1.4 传统的开发模式和敏捷开发模式的对比 1.5 敏捷开发的分类 1.5 Scrum 一. 什 ...

  7. 移动端敏捷开发之环境部署

    2019独角兽企业重金招聘Python工程师标准>>> android studio + nexus(中央仓库 远程仓库) + Jenkins(持续集成). 总结了一些以前经验和大家 ...

  8. 敏捷开发 DDD 管理

    六.十二条原则 该宣言还提出十二条敏捷开发的原则. 通过早期和持续交付有价值的软件,实现客户满意度. 欢迎不断变化的需求,即使是在项目开发的后期.要善于利用需求变更,帮助客户获得竞争优势. 不断交付可 ...

  9. 读《丰田生产方式》——联系敏捷开发的思考

    平时工作和同事讨论敏捷,曾多次听到丰田的精益思想,说是很多敏捷的想法都是从丰田的精益思想中得来的,后来在Kent Beck的<解析极限编程>里看到有这样一本书是讲丰田的精益思想的,找来看了 ...

最新文章

  1. 00-基于Vue的博客项目展示
  2. OpenKruise 如何实现应用的可用性防护?
  3. Spring集成–从头开始应用程序,第1部分
  4. TECH4ALL科技架起联结外界的桥梁,助力人与自然和谐共生
  5. 95-10-140-启动-权限
  6. 运行控制器方法之前先执行注解@ModelAttribute的方法
  7. SAP Cloud for Customer里Sales Order和Sales Quote的建模方式 1
  8. kafka 查看消费者组
  9. Echarts中国地图china.json
  10. Windows CMD常用命令大全(所见即所得)
  11. 英语流利说 核心课 level6 unit2
  12. OpenCV 3.0 高动态范围图像
  13. 开源信息安全管理平台OSSIM入门-李晨光-专题视频课程
  14. 起始方位角怎么确定_起始方位角.PPT
  15. Qt示例程序打开失败,出现一个感叹号图标
  16. hdu5651 xiaoxin juju needs help(组合数学)
  17. python计算余弦相似度
  18. 案例 | 基于JMP Pro的Lasso及岭回归在水稻全基因组预测中的应用
  19. 位运算符和位运算(与,或,异或等)
  20. Java——从前序与中序遍历序列构造二叉树

热门文章

  1. OSChina 周四乱弹 —— 国足出线了,赌球赌输了
  2. Github API:爬取Github用户数据
  3. HTML列表的简单使用以及在我们网页编程中的单位你了解多少??CSS中的字体样式你又了解多少,进来康康!!HTML、CSS(三)
  4. 【前端学习之HTMLCSS】-- 视觉格式化模型之二 浮动 -- 练习
  5. python 中的while true是什么意思_解析Python中while true的使用
  6. android 录像抠取人像,snapseed抠图教程
  7. Android怎么给图片添加备注,怎么给手机图片添加上古诗词和自己名字的小印章?...
  8. java运行applet的命令_Java工具JDK中用来运行Applet程序的命令是()
  9. 测量数据采集分析平台
  10. IB 化学考纲巨变 ,全面分析新旧考纲区别