S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则
S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写。
| SRP | The Single Responsibility Principle | 单一职责原则 |
| OCP | The Open Closed Principle | 开放封闭原则 |
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
| ISP | The Interface Segregation Principle | 接口分离原则 |
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |
一、单一职责原则(SRP)
从面向对象角度解释这个原则为:"引起类变化的因素永远不要多于一个。" 或者说 "一个类有且仅有一个职责"。这似乎不太好理解,特别是"引起类变化的因素永远不要多于一个。"这句话更是有点虚,让人有点摸不着头脑。
我们通常都说“低耦合,高内聚”。在我看来,这里的"单一职责"就是我们通常所说的“高内聚”,即一个类只完成它应该完成的职责,不能推诿责任,也不可越殂代疱,不能成为无所不能的上帝类。如果你的团队中实施宽松的“代码集体所有权”,在编码的过程中出现许多人同时修改(维护)同一个类的现象,而且成员之间的沟通不够及时,主动和畅通的话,那么时间一长,就很可能出现“承担过多职责”的上帝类。这时,提炼基类/接口和提炼类重构将能帮助我们消除或减轻这种设计臭味。
看一个例子:
这是一个违反了“单一职责原则” 的类结构图。
这里,Rectangle类做了下面两件事:
- 计算矩形面积;
- 在界面(绘制设备)上绘制矩形;
并且,有两个应用使用了Rectangle类:
- 计算几何应用程序(Computational Geometry Application)用这个类计算面积;
- 图形程序(Graphical Application)用这个类在界面上绘制矩形;
这违反了SRP(单一职责原则)。因为Rectangle类做了两件事,在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;图形应用程序中Rectangle类的变化可能导致计算几何应用程序的变化,编译和测试,反之亦然。那么,怎么修改才能让其符合单一职责原则呢?
答案是:拆分!拆分职责到两个不同的类中,如:
- Rectangle: 这个类应该只定义Area()方法;
- RectangleUI: 这个类应继承Rectangle类,并定义Draw()方法。
二、开放封闭原则 (OCP)
从面向对象设计角度看,这个原则可以这么理解:"软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。" 通俗来讲,它意味着你(或者类的客户)应当能在不修改一个类的前提下扩展这个类的行为。在OOD里,对扩展开放意味着类或模块的行为能够改变,在需求变化时我们能以新的,不同的方式让模块改变,或者在新的应用中满足需求。
也就是说,对扩展是开放的,而对修改是封闭的。我们通常都说:向系统中增加功能时应该只是添加新代码,而应该尽量少的修改原代码。在我看来,这就是遵循开放封闭原则所能带来的效果。曾经在网上看到过这样一句话“哪里变化,封装哪里”。这其实就是说,我们要将系统中可能变化的地方封装起来,即对修改封闭。同时,为了应对系统需求(功能)的扩展,需要抽象!
这里抽象是关键。《设计模式》中的state模式和strategy模式是这个原则的最好体现。
举一个例子:
违反了开放封闭原则的类结构图。
客户端代码直接面向服务器端的具体实现编程,缺乏灵活性。这样如果服务器因为某些原因被其他服务器替换了,那么客户端调用服务器的代码也必须做相应的修改或替换。这其实就是”面向实现编程“的设计臭味!
那么,如何修改才能得到正确灵活的设计?
答案是:抽象!为服务器端的代码(类型)抽象出一个抽象基类(定义一组完成服务职责的最小接口)。
下面是正确的设计:
遵循开放封闭原则的类结构图。
基本上,你抽象的东西是你系统的核心内容,如果你抽象得好,很可能增加一个新的服务器类型(扩展)只需要添加新类型(继承自AbstractServer即可)。因此代码要尽可能以抽象(这里的AbstractServer)为依据,这会允许你扩展抽象事物,定义一个新的实现而不需要修改任何客户端代码。即”面向接口编程,不要面向实现编程“!
三、Liskov's 替换原则(LSP)
Liskov's 替换原则意思是:"子类型必须能够替换它们的基类型。"或者换个说法:"使用基类引用的地方必须能使用继承类的对象而不必知道它。" 这个原则正是保证继承能够被正确使用的前提。通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计。
在基本的面向对象原则里,"继承"通常是"is a"的关系。如果"Developer" 是一个"SoftwareProfessional",那么"Developer"类应当继承"SoftwareProfessional"类。在类设计中"Is a"关系非常重要,但它容易冲昏头脑,导致使用错误的继承造成错误设计。
看一个最最经典的例子:
遵循Liskov替换原则的类结构图。
注:这里,KingFisher(翠鸟)类扩展了Bird基类,并继承了Fly()方法,这没有问题。
但是下面这个类结构图就存在设计上的问题:
违反Liskov替换原则的类结构图。
Ostrich(鸵鸟)是一种鸟,这毋庸置疑,并从Bird类继承,这从概念上说没有问题。但是鸵鸟它能飞吗?不能,那么这个设计就违反了LSP。因为在使用Bird的地方不一定能用Ostrich代替。所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类NoFlyBrid,Ostrich应该继承这个不会飞的鸟类NoFlyBrid。
为什么LSP如此重要?
- 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
- 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;
四、接口分离原则(ISP)
这个原则的意思是"客户端不应该被迫依赖于它们不用的接口。" 也就是说,一个接口或者类应该拥有尽可能少的行为(那么,什么叫尽可能少?就是少到恰好能完成它自身的职责),这也是保证“软件系统模块的粒度尽可能少,以达到高度可重用的目的。
接口包含太多的方法会降低其可用性,像这种包含了无用方法的"胖接口"会增加类之间的耦合。如果一个类想实现该接口,那么它需要实现所有的方法,尽管有些对它来说可能完全没用,所以这样做会在系统中引入不必要的复杂度,降低代码的可维护性或鲁棒性。
接口分离原则确保实现的接口有它们共同的职责,它们是明确的,易理解的,可复用的.
下面这个例子充分的说明了”接口应该仅包含必要的方法,而不该包含其它的“。如果一个接口包含了过多的方法,应该通过分离接口将其拆分。
这是一个违反接口分离原则的胖接口。
注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞)。因此,这个"胖接口"应该拆分成两个不同的接口,IBird和IFlyingBird, 而IFlyingBird继承自IBird。如下图所示:
这样的话,重用将变得非常灵活:如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird。
因此,如果我们想要获得可重用的方案,就应当遵循接口分离原则,把接口定义成仅包含必要的部分,以便在任何需要该接口功能的地方复用这个接口。
五、依赖倒置原则(DIP)
这个原则的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象。其实又是”面向接口编程,不要面向实现编程“的内在要求。
我们考虑一个现实中的例子,来看看依赖倒置原则给我们软件带来的好处。
你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?
注意:这里的 Car 就是高层模块;它依赖于抽象接口IToyotaEngine 和 IEighteenInchWheel.
而具体的引擎FifteenHundredCCEngine 属于底层模块,也依赖于抽象接口IToyotaEngine ;
具体的车轮 EighteenInchWheelWithAlloy同样属于底层模块,也依赖于抽象接口IEighteenInchWheel。
上面Car类有两个属性(引擎和车轮列表),它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。
除SOLID原则外还有很多其它的面向对象原则。如:
- "组合替代继承":这是说相对于继承,要更倾向于使用组合;
- "笛米特法则":这是说"你的类对其它类知道的越少越好";
- "共同封闭原则":这是说"相关类应该打包在一起";
- "稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";
S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则的更多相关文章
- Java成长第五集--面向对象设计的五大原则
S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写.以下图说明: 下面就个人的理解来说说这五大原则的含义到 ...
- 面向对象分析与设计—OOD部分
第三部分 面向对象设计 3.1 面向对象设计(OOD)的定义? 在面向对象分析阶段,已经针对用户需求建立起用面向对象概念描述的系统分析模型.在设计阶段,要考虑为实现系统而采用的计算机设备.操作系统.网 ...
- 初步学习JAVA面向对象初步认识及面向对象内存分析图举例说明
1. 说到面向对象, 一个绕不开的话题,就是面向过程. 面向过程适合简单.不需要协作的事务. 面向过程 = 分解问题 + 逻辑为先 = 先细节,再整体. 对比面向过程, 面向对象是模块化的, 当我们思 ...
- S.O.L.I.D: PHP 面向对象设计的五个基准原则
S.O.L.I.D 是首个 5 个面向对象设计 (OOD) 准则的首字母缩写,这些准则是由 Robert C. Martin 提出的,他更为人所熟知的名字是 Uncle Bob. 这些准则使得开发出易 ...
- SOLID (面向对象设计) 基本原则
SOLID (面向对象设计) 基本原则 在 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特•C•马丁在21世纪早期[1] 引入的记忆术首字母缩略 ...
- day24 面向对象设计part1
#!/usr/bin/env python # -*- coding:utf-8 -*- # ----------------------------------------------------- ...
- OOD 面向对象面试干货分享| 面向对象设计的SOLID原则
S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写. 简写 全拼 中文翻译 SRP The Single Res ...
- Virtools元素、类和面向对象设计
无意中发现了在某个不存在的网站( https://sites.google.com )上,还存有09年写的一些半成品教材,下面这篇文章就是其中一部分. 概述 Virtools将元素(Element)组 ...
- day24:面向对象设计与面向对象编程、类和对象
一.三大编程范式: 面向过程: 面向函数: 面向对象: 二.程序的进化论: 1.编程最开始就是无组织无结构,从简单控制流中按步写指令 2.从上述的指令中提取重复的代码块或逻辑,组织到一起(比方说,你定 ...
随机推荐
- iOS学习之Objective-C 2.0 运行时系统编程
0 导言 本主主要内容包括: 1.概述2.参考3.运行时系统的版本和平台4.和运行时系统的交互5.消息6.动态方法解析7.消息转发8.类型编码9.属性声明 1 概述 Objective-C语言将决定尽 ...
- SQL Server排名函数与排名开窗函数
什么是排名函数?说实话我也不甚清楚,我知道 order by 是排序用的,那么什么又是排名函数呢? 接下来看几个示例就明白了. 首先建立一个表,随便插入一些数据. ROW_NUMBER 函数:直接排序 ...
- JVM内存分配原理
堆栈常量池等内存分配原理详解 存储的方式: 寄存器 栈(stack) 堆(heap) 静态域 常量池 非RAM存储 JAVA寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. ...
- java poi解析excel日期为数字的问题
这个数字是什么呢?是以1900年为原点,到2015年8月21日,之间经过的天数. 知道这个后,就很好处理了,我们拿到1900年的日期,在这个日期上加上42237天即可.如下: Calendar cal ...
- 接口API中的敏感数据基于AES进行安全加密后返回
许久没有写博客了,有些惶恐地打开这个再熟悉不过的编辑器. 场景:要对一个涉及到敏感数据(账号.密码)的接口进行加密后返回 由于之前没有相关的经验,所以先在网上搜罗了一阵,这篇博客不错https://w ...
- asp.net时间显示
DateTime dt = DateTime.Now;// Label1.Text = dt.ToString();//2005-11-5 13:21:25// Label2.Text = ...
- 企业级项目把.app文件转成.ipa文件的自动化实现
将MakeIPA.sh添加到项目的根目录下 此脚本针对企业级项目打包,不会编译项目,在打包前确认项目已经编译完成生成了"XXX.app"文件 使用前需要配置该脚本部分路径才能正确打 ...
- hive报错汇总
1.需要注意的是,要在namenode(超级用户)上操作,貌似是 hive> insert into table record_partition partition(trancation_da ...
- 浅析SQL注入
body, td { font-family: calibri; font-size: 10pt; } 演示 记得以前瞎鼓捣的时候,学过一个传说中的SQL注入万能字符串,是这个样子的' or '1'= ...
- 20145331 《Java程序设计》第6周学习总结
20145331 <Java程序设计>第6周学习总结 教材学习内容总结 第十章 输入/输出 10.1.1串流 •Java将输入/输出抽象化为串流,数据有来源及目的地,衔 ...