设计模式之SOLID原则再回首
本科阶段学过设计模式,那时对设计模式的五大原则——SOLID原则的概念与理解还是比较模糊,此时过去了2年时间,在学习《高级软件工程》课程中老师又提到了设计模式,课程中还详细讨论了五大原则的过程,这次SOLID原则再回首作者提出了一些更通俗的理解吧~ 一. 什么是设计模式?那么,什么是设计模式呢?从广义角度讲设计模式是可解决一类软件问题并能重复使用的设计方案; 从狭义角度讲设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述,是在类和对象的层次描述的可重复使用的软件设计问题的解决方案. 模式体现的是程序整体的构思,也会出现在分析或者是概要设计阶段,包括创建型模式、结构型模式和行为型模式. 模式的核心思想是通过增加抽象层,把变化部分从那些不变部分里分离出来. 模式的四大基本要素包括: 1.模式名称(Pattern Name) 2.问题(Problem):描述应该在何时使用模式,解释了设计问题和问题存在的前因后果,可能还描述模式必须满足的先决条件 3.解决方案(Solution):描述了设计的组成成分、相互关系及各自的职责和协作方式.模式就像一个模板,可应用于多种场合,所以解决方案并不描述一个具体的设计或实现,而是提供设计问题的抽象描述和解决问题所采用的元素组合(类和对象). 4.效果(Consequences):描述模式的应用效果及使用模式应权衡的问题. 其中设计模式 的SOLID原则(Principles)如下: 单一职责原则(Single Responsibility) 开闭原则(Open Closed) 里氏代换原则(Liskov Substitution) 接口隔离原原则(Interface Segregation) 依赖倒置原则(Dependency Inversion) 设计模式就是实现了上述原则,从而达到代码复用、增加可维护性的目的. 二. 单一职责原则 单一职责原则(Single Responsibility Principle,SRP)思想:就一个类而言,应仅有一个引起它变化的原因(一个类只有一个职责);每一个引起类变化的原因就是一个职责,当类具有多职责时,应该把多余职责分离出去,分别创建类来完成. 正如《大话设计模式》中所说:“如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力.这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏.”而且在软件设计过程中需要完成的内容非常多,发现职责并把那些职责相互分离还是有难度的. 例子理解: 比如C#Winform设计俄罗斯方块游戏时,就需要把界面变化(方块绘制擦除)和游戏逻辑(方块下落旋转碰撞移动等)分离,至少应该讲程序分为两个类——游戏逻辑类和WinForm窗体界面类.当需要改变界面时,只需修改窗体类,和游戏逻辑无关,而且游戏逻辑是不太容易变化的,将它们分离有利于界面的修改,从而达到代码复用的目的. 编程过程中我们要在类的职责分离上多思考,做到单一职责,这样你的代码才是真正的易维护、易扩展、易复用、灵活多样. 三. 开闭原则 开闭原则(Open Closed Principle,OCP)思想:对功能扩展是开放的(增加新功能),但对修改原功能代码是关闭的.在进行扩展(类模块函数都可以扩展)时,不需要对原来的程序进行修改,即已有东西不变,可添加新功能.
PS:想到以前图像处理软件,每学习一章新处理如图像灰度、增强、采样、二值化等,就在原来的基础上不断增加新的函数实现,而且最后一个.cpp文件4000多行,简直无法直视啊!可能还是我实际项目做得比较少的原因,拿到一个项目如何去规划、设计的理解依然不够,等工作几年后我再回来写一些实际经验的设计思想文章吧,拭目以待~
切记:开闭原则是面向对象设计的核心所在,这个原则可以带来面向对象技术所声称的巨大好处,即可维护、可扩展、可服用、灵活性好,开发人员对程序中呈现出频繁变化的那些部分作出抽象,然而对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意,拒绝不成熟的抽象和抽象本身一样重要. 四. 里氏替换原则 里氏替换原则(Liskov Substitution Principle,LSP)思想:继承必须确保父类所拥有的性质在子类中仍然成立,当一个子类的实例能够替换任何其父类的实例时,它们之间才具有is-a-kind-of-A关系.只有当一个子类(派生类)可以替换掉其父类(基类)而软件功能不受影响时,基类才能被复用,派生类也才能在基类的基础上增加新的行为. 再如动物的例子,动物具有吃、喝、跑、叫等行为,如果需要其他动物也有类似行为,由于它们都继承于动物,只需要更改实例化的地方,程序其他不许改变.如下图所示: 五.依赖倒置原则依赖倒置原则(Dependence Inversion Principle)思想:高层模块不应该依赖低层模块,二者都应该依赖于抽象.1.高层模块只应该包含重要的业务模型和策略选择 2.低层模块则是不同业务和策略的实现 3.高层抽象不依赖高层和底层模块的具体实现,最多依赖低层的抽象 4.低层抽象和实现也只依赖于高层抽象 换句话说:要依赖于抽象,而不要依赖于具体实现;高层是功能和策略,低层是具体实现;编程程序语言就是需要针对接口编程而不要针对实现编程. 顺便说说与设计模式相关的两个知识点: 1.面向对象的四个优点:可维护、可扩展、可服用、灵活性好 2.高内聚低耦合:高内聚就是一个模块内各个元素彼此结合紧密程度高,一个软件模块只负责一个任务,也就是单一职责原则,提高模块的独立性;低耦合就是软件结构内不同模块之间相互连接的程度低,模块与模块之间尽可能独立存在,模块与模块之间的接口尽量少而且简单,具有更好的复用性和扩展性. 例子理解:(强推&参考博客园cbf4life的文章——依赖倒置原则) 下图表示司机驾驶奔驰车的类图.
代码如下所示,司机通过调用奔驰车run方法开动汽车,通过client场景描述eastmount开奔驰车.
//司机类 public class Driver { //司机主要职责驾驶汽车 public void drive(Benz benz) { benz.run(); } } //奔驰类 public class Benz { public void run() { System.out.printIn("奔驰汽车跑"); } } //场景类 public class Client { public static void main(String[] args) { //eastmount开奔驰车 Driver eastmount = new Driver(); Benz benz = new Benz(); eastmount.drive(benz); } }现在司机eastmount不仅要开奔驰车,还要开宝马车,又该怎么实现呢?自定义宝马汽车类BMW,但是不能开,因为eastmount没有开宝马车的方法,出现的问题就是: 司机类和奔驰车类之间是一个紧耦合关系,这导致了系统的可维护性大大降低,增加新的车类如宝马BWM汽车就需要修改司机类drive()方法,这不是稳定性而是易变的.被依赖者的变化竟然让依赖者来承担修改的代价,所以可以通过依赖倒置原则实现. 如下图所示,通过建立两个接口IDriver和ICar,分别定义了司机的职能就是驾驶汽车,通过drive()方法实现;汽车的职能就是运行,通过run()方法实现. //司机接口 驾驶汽车 抽象 public interface IDriver { public void drive(ICar car); } //司机类 具体实现 public class Driver implements IDriver{ //司机的主要职责就是驾驶汽车 public void drive(ICar car){ car.run(); } } //汽车接口 public interface ICar { public void run(); } //奔驰车类 public class Benz implements ICar{ public void run(){ System.out.println("奔驰汽车跑"); } } //宝马车类 public class BMW implements ICar{ public void run(){ System.out.println("宝马汽车跑"); } } //实现eastmount开宝马车 public class Client { public static void main(String[] args) { IDriver eastmount = new Driver(); ICar bmw = new BMW(); eastmount.drive(bmw); } }由于"抽象不应该依赖细节",所以我们认为抽象(ICar接口)不依赖BMW和Benz两个实现类(细节),在高层次的模块中应用都是抽象,Client就属于高层次业务逻辑,它对于低层次模块的依赖都是建立在抽象上,eastmount都是以IDriver接口类型进行操作,屏蔽了细节对抽象的影响,现在开不同类型的车,只需要修改业务场景类即可实现: ICar benz = new Benz(); eastmount.drive(benz); 依赖倒置其实可以说是面向对象是设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止与抽象类或接口,那就是面向对象的设计,反之则是过程化的设计. 六.接口隔离原则 接口隔离原则(Interface Segregation Principle,ISP)思想:多个和客户相关的接口要好于一个通用接口.如果一个类有几个使用者,与其让这个类再如所有使用这需要使用的所有方法,还不如为每个使用者创建一个特定接口,并让该类分别实现这些接口. 可以发现接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用,实现类中都必须去实现这些方法,如类B中方法4和方法5,类D中方法2和方法3,显然这样的设计不适用. 设计如下修改为符合接口隔离原则,必须对接口I进行拆分,拆分为三个接口I1I2I3.此时实现类B和类D中方法都是需要使用的方法,这就体现了建立单一接口而不是庞大接口的好处.(源码见上面的链接)
接口隔离原则即尽量细化接口,接口中的方法尽量少,为各个类建立专用的接口,而不是试图去建立一个庞大而臃肿的接口提供所有依赖于他的类去调用.说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。
其一,单一职责原则原注重的是职责,而接口隔离原则注重对接口依赖的隔离. 其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建.
总结:这是一篇关于回顾设计模式SOLID五大原则的文章,我非常喜欢文章中的例子,每个例子都是我精选了描述模式的,通过Modom讲述了单一职责原则、加减法计算器讲述了开闭原则、企鹅动物讲述了里氏替换原则、通过Driver和Car实现了依赖倒置原则,最后讲述了接口隔离原则.希望文章对大家有所帮助,尤其是学习设计模式的同学和代码写得不太规范或需要重构的同学,如果有错误或不足之处,还请海涵~
(By:Eastmount 2014-11-29 晚上8点 http://blog.csdn.net/eastmount/) 文章参考: 1.<<高级软件工程>>设计模式部分课件及老师讲述内容 2.<<大话设计模式>> 程杰著 3.博客园cbf4life的文章——依赖倒置原则 4.zhengzhb博客——接口隔离原则 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 什么是XML的可见性? dimen,string等(Android)
- 正则表达式使用案例-OCP试题
- 运行NuGet pack命令时,我收到错误:’AutoMapper’已经为’
- c# – ORA-06550:第1行第7列:PLS-00306:参数的数量或类型
- jaxb xml序列化与Java类的转化
- "WEAK, STRONG, UNOWNED, 老天爷!" - Swift中的引用
- mini2440 Norflash驱动移植过程
- c – Qt 5无法在SnowLeopard上部署
- 详解Vue中添加过渡效果
- [Swift]LeetCode328. 奇偶链表 | Odd Even Linked List