编码原则总结:面向对象设计的SOLID原则
S.O.L.I.D是 面向对象设计和编程(OOD&OOP)中几个重要的编码原则(Programming Priciple)的首字母缩写
缩写 | 全称 | 中文 |
---|---|---|
SRP | The Single Responsibility Principle | 单一责任原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
(1)单一责任原则 SRP
当需要修改某个类的时候,原因有且只有一个。保证让一个类只承担一种类型的责任,当这个类需要承当其他类型的责任的时候,就需要拆分这个类。
我们通常都说“低耦合,高内聚”。在我看来,这里的”单一职责”就是我们通常所说的“高内聚”,即一个类只完成它应该完成的职责,不能推诿责任,也不可越殂代疱,不能成为无所不能的上帝类。提炼 基类/接口和 提炼类重构 将能帮助我们消除或减轻这种设计臭味。
看一个例子:
这是一个违反了“单一职责原则” 的类结构图。
这里,Rectangle类做了下面两件事:
- 计算矩形面积;
- 在界面(绘制设备)上绘制矩形;
并且,有两个应用使用了Rectangle类:
- 计算几何应用程序(Computational Geometry Application)用这个类计算面积;
- 图形程序(Graphical Application)用这个类在界面上绘制矩形;
这违反了SRP(单一职责原则)。因为Rectangle类做了两件事,在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;图形应用程序中Rectangle类的变化可能导致计算几何应用程序的变化,编译和测试,反之亦然。那么,怎么修改才能让其符合单一职责原则呢?
答案是:拆分!拆分职责到两个不同的类中,如:
- Rectangle: 这个类应该只定义Area()方法;
- RectangleUI: 这个类应继承Rectangle类,并定义Draw()方法。
(2)开放封闭原则 OCP
即软件实体(类,模块(一般是动态链接库),方法,接口)应该是可扩展的,而不可修改的。也就是说,对扩展是开放的,对修改是封闭的。通俗来讲,它意味着你(或者类的客户)应当能在不修改一个类的前提下扩展这个类的行为。在OOD里,对扩展开放意味着类或模块的行为能够改变,在需求变化时我们能以新的,不同的方式让模块改变,或者在新的应用中满足需求。
也就是说,对扩展是开放的,而对修改是封闭的。我们通常都说:向系统中增加功能时应该只是添加新代码,而应该尽量少的修改原代码。在我看来,这就是遵循开放封闭原则所能带来的效果。曾经在网上看到过这样一句话“哪里变化,封装哪里”。这其实就是说,我们要将系统中可能变化的地方封装起来,即对修改封闭。同时,为了应对系统需求(功能)的扩展,需要抽象!
这里抽象是关键。《设计模式》中的state模式和strategy模式是这个原则的最好体现。
穿外套的时候不需要胸部手术。即对穿外套这一扩展是开放的,对胸部手术这一修改性操作是封闭的
举一个反例子:违反了开放封闭原则的类结构图。
客户端代码直接面向服务器端的具体实现编程,缺乏灵活性。这样如果服务器因为某些原因被其他服务器替换了,那么客户端调用服务器的代码也必须做相应的修改或替换。这其实就是”面向实现编程“的设计臭味!
那么,如何修改才能得到正确灵活的设计?
答案是:抽象!为服务器端的代码(类型)抽象出一个抽象基类(定义一组完成服务职责的最小接口)。
下面是正确的设计:遵循开放封闭原则的类结构图。
基本上,你抽象的东西是你系统的核心内容,如果你抽象得好,很可能增加一个新的服务器类型(扩展)只需要添加新类型(继承自AbstractServer即可)。因此代码要尽可能以抽象(这里的AbstractServer)为依据,这会允许你扩展抽象事物,定义一个新的实现而不需要修改任何客户端代码。即”面向接口编程,不要面向实现编程“!
(3)里氏替换原则 LSP
当一个子类的实例应该能够替换任何其超类的实例时,他们之间才具有is-A关系
意思是:”子类型必须能够替换它们的基类型。”或者换个说法:”使用基类引用的地方必须能使用继承类的对象而不必知道它。” 这个原则正是保证继承能够被正确使用的前提。通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计。
如果它看起来像一个鸭子,叫声像一个鸭子,但是需要电池,你可能就有了一个错误的抽象。
- 看一个最最经典的例子:遵循Liskov替换原则的类结构图。
注:这里,KingFisher(翠鸟)类扩展了Bird基类,并继承了Fly()方法,这没有问题。
违反Liskov替换原则的类结构图。
Ostrich(鸵鸟)是一种鸟,这毋庸置疑,并从Bird类继承,这从概念上说没有问题。但是鸵鸟它能飞吗?不能,那么这个设计就违反了LSP。因为在使用Bird的地方不一定能用Ostrich代替。所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类NoFlyBrid,Ostrich应该继承这个不会飞的鸟类NoFlyBrid。
为什么LSP如此重要?
- 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
- 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;
(4)依赖倒置原则 DIP
1.高层模块不应该依赖于底层模块,二者都应该依赖于抽象,其实又是”面向接口编程,不要面向实现编程“的内在要求。
2.抽象不应该依赖于细节,而细节应该依赖于抽象
你会直接将灯泡焊接到墙上的电线上吗?
我们考虑一个现实中的例子,来看看依赖倒置原则给我们软件带来的好处。
你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?
注意:这里的 Car 就是高层模块;它依赖于抽象接口IToyotaEngine 和 IEighteenInchWheel.
而具体的引擎FifteenHundredCCEngine 属于底层模块,也依赖于抽象接口IToyotaEngine ;
具体的车轮 EighteenInchWheelWithAlloy同样属于底层模块,也依赖于抽象接口IEighteenInchWheel。
上面Car类有两个属性(引擎和车轮列表),它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。
(5)接口分离原则 ISP
不要强迫用户去依赖不使用的接口。即使用多个专门的接口比使用单一的总接口总要好。
比如说设计了接口A,被类B和C同时继承了,然后B需要一个新功能,而C不需要,这个时候不应该是在接口A中增加方法,应该是重新设计一个 接口D,让B继承D,而C不应该做任何的修改。
看一个例子:
这个例子充分的说明了”接口应该仅包含必要的方法,而不该包含其它的“。如果一个接口包含了过多的方法,应该通过分离接口将其拆分。
这是一个违反接口分离原则的胖接口。
注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞)。因此,这个”胖接口”应该拆分成两个不同的接口,IBird和IFlyingBird, 而IFlyingBird继承自IBird。如下图所示:
这样的话,重用将变得非常灵活:如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird。
因此,如果我们想要获得可重用的方案,就应当遵循接口分离原则,把接口定义成仅包含必要的部分,以便在任何需要该接口功能的地方复用这个接口。
参考:
http://blog.csdn.net/e5Max/article/details/8872182(*)
《如何向妻子解释OOD译文链http://www.cnblogs.com/niyw/archive/2011/01/25/1940603.html
原文链接http://www.codeproject.com/Articles/93369/How-I-explained-OOD-to-my-wife
http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html
扩充:
除SOLID原则外还有很多其它的面向对象原则。如:
- “组合替代继承”:这是说相对于继承,要更倾向于使用组合;
- “笛米特法则”:这是说”你的类对其它类知道的越少越好”;
- “共同封闭原则”:这是说”相关类应该打包在一起”;
- “稳定抽象原则”:这是说”类越稳定,越应该由抽象类组成”;
当然,这些原则并不是孤立存在的,而是紧密联系的,遵循一个原则的同时也就遵循了另外一个或多个原则;反之,违反了其中一个原则也很可能同时就违反了另外一个或多个原则。 设计模式是这些原则在一些特定场景的应用结果。因此,可以把设计模式看作”框架”,把OOD原则看作”规范”。 在学习设计模式的过程中,我们要经常性的反思,这个设计模式体现了面向对象设计原则中的哪个或哪一些原则。
(6)合成复用原则(CRP)
合成复用原则(Composite Reuse Principle,CRP),即优先使用委托而不是继承来重用已用功能(代码)。循序这一原则通常也是避免触犯里氏替换原则所要求的。
(7)迪米特法则(LoD / LKP)
迪米特法则(Law Of Demeter)又称最小知识原则(Least Knowledge Principle, LKP)。意思是一个对象应当对其它对象有尽量好的了解,即应该保持对象间有尽量少的相互作用是,使得对象(类)具有好的独立性,可测试性,也就易于维护。
关于“迪米特法则”的其它表述还有:只与你的朋友们通信,不要与“陌生人”说话。
设计模式中的Facade模式和Mediator模式就是使用了这一原则,降低模块间的耦合。
编码原则总结:面向对象设计的SOLID原则相关推荐
- solid 设计原则 php,面向对象设计SOLID五大原则
今天我给大家带来的是面向对象设计SOLID五大原则的经典解说. 我们知道,面向对象对于设计出高扩展性.高复用性.高可维护性的软件起到很大的作用.我们常说的SOLID五大设计原则指的就是: S = 单一 ...
- 面向对象设计的七大原则 (包括SOLID原则)
文章目录 概述 1. 单一原则 2. 里氏替换原则 3. 依赖倒转原则 4. 接口分隔原则(Interface Segregation Principle ,ISP) 5. 迪米特法则 (Law of ...
- 面向对象设计与开发原则
介绍 这里介绍了5个面向对象设计与开发原则–SOLID原则,分别是:单一职责原则.开放封闭原则.里氏替换原则.接口隔离原则.依赖倒置原则.另外还介绍了其他3个原则:迪米特法则."Tell, ...
- 深入理解面向对象设计的七大原则
一.面向对象设计的七大原则是什么? 1.开放封闭原则 2.里氏转换原则 3.依赖倒转原则 4.组合/聚合原则 5.接口隔离原则 6."迪米特"法则 7.单一职责原则 二.七大原则是 ...
- PHP面向对象设计的五大原则
面向对象设计的五大原则:单一职责原则.接口隔离原则.开放-封闭原则.替换原则.依赖倒置原则.这些原则主要是由Robert C.Martin在<敏捷软件开发--原则.方法.与实践>一书中总结 ...
- java中高级面试_中高级面试常问:Java面向对象设计的六大原则
这篇文章主要讲的是面向对象设计中,我们应该遵循的六大原则.只有掌握了这些原则,我们才能更好的理解设计模式.我们接下来要介绍以下6个内容.单一职责原则--SRP 开闭原则--OCP 里式替换原则--LS ...
- 61条面向对象设计的经验原则
61条面向对象设计的经验原则 摘抄自<OOD 启思录>--Arthur J.Riel 著 鲍志云 译 "你不必严格遵守这些原则,违背它们也不会被处以宗教刑罚.但你应当把这些原则看 ...
- 设计模式(面向对象)设计的七大原则
声明:本人设计模式模块是集合网上资料和老师课件总结的知识点,如本博客有侵权,本人即刻删. 设计模式(面向对象设计)原则,分别是: 1.开放封闭原则:对扩展开放,对修改关闭 2.单一职责原则:一个类只做 ...
- 设计模式-合成复用原则-》面向对象设计原则
合成复用原则是面向对象设计原则的 7 条原则中剩下的最后一条,下面我们将对其进行详细地介绍. 合成复用原则的定义 合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚 ...
最新文章
- Storm原理与实践--大数据技术栈14
- 剑指云内存数据库,阿里云在下一盘大棋
- 定位域中长期不活动计算机
- 第四节 莎士比亚模板
- WordPress在Permalink取消index.php后nginx404的解决方案
- HTML+CSS+JavaScript复习笔记持更(二)——列表篇
- (todo)数组名 有存储空间吗?
- 写给准备参加秋招的学弟学妹们~一定要来看哦~
- SpringBoot2.6.1 elasticsearch7.1.5 Vue
- 凯斯西储计算机科学,凯斯西储大学电气工程与计算机科学系基本信息详解
- WPF 3D:简单的Point3D和Vector3D动画创造一个旋转的正方体
- Process finished with exit code-1073740791(0xC0000409)
- 智能硬件可能成为网络安全事件新的“爆发点”
- python unpack java,Java中的python struct.unpack - java
- 微信是如何做用户体验的?
- 做计算机的小卫士教案,小学信息技术教案四年级上环保小卫士
- 解决OpenCV读取图片慢的方案
- scada与MySQL连接_SCADA系统数据库连接功能设计及应用
- python运行代码示例_python程序样例
- 微服务-消息总线 SpringCloud Bus
热门文章
- R语言学习记录:主成分分析的R实现
- Nginx 负载均衡动静分离配置
- 手把手教你用GoEasy实现Websocket IM聊天
- NDK Caused by: java.lang.UnsatisfiedLinkError:
- C#设计模式——访问者模式(Vistor Pattern)
- 浙大图灵班今年首次招生:院士授课,本科生配学业导师
- 华为电脑如何投屏到电视linux,华为 P30 如何投屏到电脑
- 剑指offe55--链表中环的入口结点
- 零基础想学大数据?别急!先搞清这一点
- java 判断经纬度_Java中根据经纬度来判断距离