`

面向对象编程solid原则

阅读更多
  • 单一职责(Single Responsibility):类和对象最好是只有单一的职责,在程序设计中如果发现某些类存在多个职责,可以考虑进行拆分;
  • 开闭原则(Open-close, open for extension, close for modification):设计要对扩展开放,对修改关闭,换句话说,程序应保证平滑的扩展性,尽量避免因为新增同类功能而修改现有设计,这样可以少产出些回归问题;
  • 里式替换(Liskov Substitution):这是面向对象的基本要素之一,凡是可以使用父类或者基类的地方,都可以用子类替换;
  • 接口分离(Interface Segregation):在我们进行接口的设计时,如果一个接口定义了太多方法,那么在某个类实现这个接口时,可能面临两难,某些方法是不用的,这就破坏了程序的内聚性;
    对于这种情况,可以将功能单一的接口进行拆分,这样,在添加新功能时可以在不改变原有接口的基础上进行功能的扩展;
  • 依赖反转(Dependency Inversion):实体应该依赖于抽象而不是实现,也就是说高层次的模块,不应该依赖于低层次模块,而是应该基于抽象,实践这一原则是保证代码适当耦合度的法宝。

一、单一职责原则(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原则外还有很多其它的面向对象原则。如:

"组合替代继承":这是说相对于继承,要更倾向于使用组合;

"笛米特法则":这是说"你的类对其它类知道的越少越好";

"共同封闭原则":这是说"相关类应该打包在一起";

"稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";

当然,这些原则并不是孤立存在的,而是紧密联系的,遵循一个原则的同时也就遵循了另外一个或多个原则;反之,违反了其中一个原则也很可能同时就违反了另外一个或多个原则。 设计模式是这些原则在一些特定场景的应用结果。因此,可以把设计模式看作"框架",把OOD原则看作"规范"。 在学习设计模式的过程中,我们要经常性的反思,这个设计模式体现了面向对象设计原则中的哪个或哪一些原则。

特别是在重构实现模式,或重构趋向模式的过程中,我们更要结合SOLID原则思考代码在重构前后的区别,理解它的改进。我正在研读《重构与模式》一书,因此接下来我会结合例子记录自己的学习体会。

注:以上图片均来自《如何向妻子解释OOD》译文链接:http://www.cnblogs.com/niyw/archive/2011/01/25/1940603.html

<How I explained OOD to my wife> 原文链接:http://www.codeproject.com/Articles/93369/How-I-explained-OOD-to-my-wife

还有两个常被提到的原则:

六、合成复用原则(CRP)

合成复用原则(Composite Reuse Principle,CRP),即优先使用委托而不是继承来重用已用功能(代码)。循序这一原则通常也是避免触犯里氏替换原则所要求的。

七、迪米特法则(LoD / LKP)

迪米特法则(Law Of  Demeter)又称最小知识原则(Least Knowledge Principle, LKP)。意思是一个对象应当对其它对象有尽量好的了解,即应该保持对象间有尽量少的相互作用是,使得对象(类)具有好的独立性,可测试性,也就易于维护。

关于“迪米特法则”的其它表述还有:只与你的朋友们通信,不要与“陌生人”说话。设计模式中的Facade模式和Mediator模式就是使用了这一原则,降低模块间的耦合

分享到:
评论

相关推荐

    《面向对象的程序设计》期末试卷(A)答案.pdf

    类(Class)是面向对象编程的基本单元,它是一个蓝图,描述了创建对象时共有的属性和方法。对象(Object)是类的实例,具有类定义的属性和方法的副本。类的属性和方法称为成员(Members)。 2. 封装 封装...

    面向对象设计的SOLID原则 .docx

    SOLID 原则是面向对象设计和编程(OOD&OOP)中几个重要编码原则的首字母缩写,分别是单一责任原则、开放封闭原则、里氏替换原则、依赖倒置原则和接口分离原则。这些原则是非常基础而且重要的面向对象设计原则,理解...

    Java面向对象程序设计教程.pdf

    学习Java面向对象编程不仅仅是掌握语法,更重要的是理解其设计理念和原则,如SOLID原则(单一职责、开闭、里氏替换、接口隔离、依赖倒置),以及设计模式的应用,如工厂模式、单例模式、观察者模式等。这些都将在...

    C++ 面向对象程序设计(第七版) 周靖 译

    《C++ 面向对象程序设计》是周靖翻译的第七版教材,该书深入浅出地介绍了C++这门强大的编程语言,特别强调了面向对象编程的概念和实践。面向对象编程(Object-Oriented Programming,OOP)是C++的核心特性,它通过类...

    JAVA面向对象程序设计源代码

    在Java编程语言中,面向对象程序设计(Object-Oriented...通过学习这些源代码和配套的PPT,初学者可以深入理解Java面向对象编程的概念,逐步提高编程技能。同时,实践这些例子可以帮助巩固理论知识,提升问题解决能力。

    面向对象 面向对象 面向对象

    1. **对象和类**:在面向对象编程中,对象是程序的基本单元,它包含了数据(属性)和操作数据的方法(行为)。类是对具有相同属性和行为的对象的抽象,是创建对象的模板。通过定义类,我们可以描述对象的结构和行为...

    面向对象设计的基本原则和实践建议

    6. ** SOLID原则**:以上五条原则合称为SOLID原则,是面向对象设计的基础。它们提供了一种设计高质量、易于维护和扩展的软件系统的指导方针。 在实践中,还有一些其他重要的建议: 7. **使用设计模式**:设计模式...

    面向对象的11个原则

    面向对象设计原则是软件开发中的一种指导思想,它旨在提高代码的可维护性、可扩展性和可复用性。SOLID原则是这些原则的核心,它包括五个主要的方面:单一职责原则、开放/关闭原则、里氏替换原则、依赖倒置原则和接口...

    面向对象课程设计

    面向对象课程设计是一种重要的计算机科学教学实践,旨在让学生掌握面向对象编程的概念、原则和方法。在本课程设计中,学生将运用面向对象的思想来解决实际问题,例如构建一个同城二手交易平台。这个选题是一个典型的...

    循序渐进学面向对象系统设计PDF,本书是一本介绍面向对象软件系统设计的书,还讲述了UML等概念。

    面向对象系统设计是一种重要的软件开发方法,它基于面向对象编程(OOP)的思想,通过将现实世界中的问题抽象为类和对象,实现软件系统的模块化和可维护性。《循序渐进学面向对象系统设计》这本书正是为了帮助读者...

    php面向对象程序设计类

    类是面向对象编程的基本单位,它封装了数据和操作这些数据的方法。在PHP中,我们可以使用`class`关键字来定义一个类。例如: ```php class MyClass { // 属性 public $property; // 方法 public function ...

    面向对象程序设计 理工版

    C++ 是一种广泛应用于系统软件开发、游戏编程、嵌入式系统等领域的面向对象编程语言,它支持过程化编程、面向对象编程以及模板编程等多种编程模式。 在OOP中,有四个核心概念:封装、继承、多态和抽象。 1. 封装:...

    面向对象程序设计——入门

    3. **Java中的面向对象编程** - **类定义**:使用class关键字定义类,包括属性(成员变量)和方法(成员函数)。 - **对象创建**:通过new关键字和构造函数实例化对象。 - **访问控制**:了解public, private, ...

    Ruby 面向对象设计实践--2013年

    《Ruby面向对象设计实践》不仅讲解了面向对象编程的基本概念和技术,还深入探讨了如何应用设计原则和设计模式来编写更高质量的代码。 ##### 1. SOLID原则 - **单一职责原则**(Single Responsibility Principle, ...

    面向对象七大基本设计原则.pdf

    面向对象七大基本设计原则通常是指SOLID原则,它是一组面向对象设计的指导原则,旨在使软件更加可维护和可扩展。SOLID由以下五个原则组成: 1. 单一职责原则(Single Responsibility Principle, SRP):一个类应该...

    跟媳妇解释设计模式与面向对象

    在面向对象编程中,SOLID原则是非常重要的,它是一组面向对象设计原则的首字母缩写,包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。遵循这些原则,可以使程序更加健壮、易于维护和扩展。 ...

    c++面向对象程序设计ppt.zip

    学习C++面向对象编程,不仅要掌握语法,更需要理解面向对象的设计原则,如SOLID原则(单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则)。同时,通过编写实际项目,实践面向对象设计模式(如工厂...

    38丨 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点1

    在面向对象编程中,遵循一定的设计原则如 SOLID(单一职责原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则)可以帮助我们编写出高质量的代码。编程规范,如命名规则、注释标准、代码格式等,有助于团队...

    10. 理解面向对象编程11

    面向对象编程(Object-Oriented Programming,简称OOP)是一种重要的编程范式,它强调将数据和操作数据的方法封装在对象中,通过对象之间的交互来完成任务。在Python这样的面向对象语言中,OOP能够帮助我们构建更加...

    面向对象软件构造(第二版)中英对照版

    《面向对象软件构造》是软件工程领域的一本经典著作,主要介绍了面向对象编程与设计的基本原理和实践方法。第二版在此基础上进行了更新和扩展,涵盖了更广泛的面向对象技术及其应用。以下是该书第一章到第十章的主要...

Global site tag (gtag.js) - Google Analytics