关于对象设计的几个原则

8  单一职责原则SRP

8.1  单一职责原则(SRP

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

8.1.1  什么是职责

SRP中,我们把职责定义为“变化的原因”(a reason for change)。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。有时,我们很难注意到这一点。我们习惯于以组的形式去考虑职责。

8.2     

SRP是所有原则中最简单的之一,也是最难正确运用的之一。我们会自然地把职责结合在一起。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。

9  开放-封闭原则OCP

9.1  开放-封闭原则(OCP

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

如果程序中的一处改动就会产生连锁反应,导致一系列相关模块的改动,那么设计就具有僵化性的臭味。OCP建议我们应该对系统进行重构,这样以后对系统再进行那样的改动时,就不会导致更多的修改。如果正确地应用OCP,那么以后再进行同样的改动时,就只需要添加新的代码,而不必改动已经正常运行的代码。

9.2     

遵循开放-封闭原则设计出的模块具有两个主要的特征。它们是:

1.        “对于扩展是开放的”(Open for extension)。

这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。

2.        “对于更改是封闭的”(Closed for modificaiton)。

对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java.jar文件,都无需改动。

9.4     

在许多方面,OCP都是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处(也就是,灵活性、可重用性以及可维护性)。然而,并不是说只要使用一种面向对象语言就是遵循了这个原则。对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。

10  Liskov替换原则LSP

10.1  Liskov替换原则(LSP

对于LSP可以做如下解释:

子类型(subtype)必须能够替换掉它们的基类型(base type)。

Barbara Liskov首次写下这个原则是在1988年。她说道,

这里需要如下替换性质:若对每个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,用o1替换o2后,程序P行为功能不变,则ST的子类型。

想想违反该原则的后果,LSP的重要性就不言而喻了。假设有一个函数f,它的参数为指向某个基类B的指针(pointer)或者引用(reference)。同样假设有B的某个派生类D,如果把D的对象作为B类型传递给f,会导致f出现错误的行为。那么D就违反了LSP。显然,D对于f来说是脆弱的。

10.7     

OCPOOD中很多说法的核心。如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及健壮性。LSP是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。因此,如果没有显式地强制基类类型的契约,那么代码就必须良好地并且明显地表达出这一点。

术语“IS-A”的含意过于宽泛以至于不能作为子类型的定义。子类型的正确定义是“可替换性的”,这里的可替换性可以通过显式或者隐式的契约来定义。

11  依赖倒置原则DIP

11.1  依赖倒置原则(DIP

a. 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。

b. 抽象不应该依赖于细节。细节应该依赖于抽象。

一个稍微简单但仍然非常有效的对于DIP的解释,是这样一个简单的启发式规则:“依赖于抽象。”这是一个简单的陈述,该启发式规则建议不应该依赖于具体类——也就是说,程序中所有的依赖关系都应该终止于抽象类或者接口。

根据这个启发式规则,可知:

l         任何变量都不应该持有一个指向具体类的指针或者引用

l         任何类都不应该从具体类派生

l         任何方法都不应该覆写它的任何基类中的已经实现了的方法

11.5     

使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。这是糟糕的,因为这样会使策略受到细节改变的影响。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。

事实上,这种依赖关系的倒置正是好的面向对象设计的标志所在。使用何种语言来编写程序是无关紧要的。如果程序的依赖关系是倒置的,它就是面向对象的设计。如果程序的依赖关系不是倒置的,它就是过程化的设计。

依赖倒置原则是实现许多面向对象技术所宣称的好处的基本低层机制。它的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的。由于抽象和细节被彼此隔离,所以代码也非常容易维护。

12  接口隔离原则ISP

12.3  接口隔离原则(ISP

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

    如果强迫客户程序依赖于那些它们不使用的方法,那么这些客户程序就面临着由于这些未使用方法的改变所带来的变更。这无意中导致了所有客户程序之间的耦合。换种说法,如果一个客户程序依赖于一个含有它不使用的方法的类,但是其他客户程序却要使用该方法,那么当其他客户要求这个类改变时,就会影响到这个客户程序。我们希望尽可能地避免这种耦合,因此我们希望分离接口。

12.6     

胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定于客户程序的接口,可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户程序的接口,并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,并使客户程序之间互不依赖。

http://www.kynj.com.cn/blog/article.asp?id=75

我们传统的结构化编程中,最上层的模块通常都要依赖下面的子模块来实现,也
称为高层依赖低层!
所以DIP原则就是要逆转这种依赖关系,让高层模块不要依赖低层模块,所以称之为依赖倒置原则!
DIP原则,我们可以从2点来解读:
第1点:高层模块不依赖底层模块,两者都依赖抽象
第2点:抽象不应该依赖于细节,细节应该依赖于抽象

上面这2点,也是教科书这么定义的,读上去比较抽象点!下面我会讲点自己的心得,和大家研究研究!不对的地方还请指教!

依照上面2点来做OOP,原则上可以解决高层的复用问题以及底层改动会波及到高层,形成赖传递的问题!

我百度了一下,找了不少解释,有一种讲解比较流行,但个人觉得有点问题:
--------原文是这样的---------------
依赖倒置(Dependence Inversion Principle)原则讲的是:要依赖于抽象,不要依赖于具体。
简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:
抽象不应当依赖于细节;细节应当依赖于抽象;
要针对接口编程,不针对实现编程。

反面例子:

uploads/200604/29_164010_20051027143100352.gif


缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。

解决办法一:
将Light作成Abstract,然后具体类继承自Light。

uploads/200604/29_164137_20051027143101237.gif

优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与Tube

Light继承自Light,可以根据"开放-封闭"原则进行扩展。只要Light不发生变化,BulbLight与TubeLight的变化
就不会波及ToggleSwitch。

缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。

解决方法二:

uploads/200604/29_164244_20051027143101437.gif

优点:更为通用、更为稳定。

结论:
使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为

策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定

性决定了系统的稳定性。


---------------原文结束-----------

正如他的UML图所示
在ToggleSwitch依赖于抽象类Light,那么在ToggleSwitch在实例化的时候,照他

的UML那样,肯定得如下写:Light light=new BulbLight() 或者 new

TubeLight()
那这样,又把底层的依赖给传递到高层了!这和初衷相违背的!
应该按照OO的原则,哪里有变化,就封装那里!
所以我觉得在ToggleSwitch和Light之间应该有个封装类,来封装这个变化点!
正如GOF创建型模式中的工厂模式!

总言之,这个原则的本质就是用抽象(接口或者抽象类)来使高层模块独立于底层模块!以达到高层的自由复用!

其实不管是SRP好,OCP也好,还是LSP也好,都能够从DIP推演出来,而所有的这些原则的本质就是强调抽象,强调在高低层之间用上
一个抽象的中间层!以期降低模块间的耦合性!

http://blog.csdn.net/sophia_sy/archive/2007/01/17/1485797.aspx

DIP依赖倒置原则

DIP依赖倒置原则,是解决层间耦合的一个方法。假设有两个层,每个层各有一个类:A和B。A在上层,B在下层。A需要用到B的方法。

/*A.h*/

#include "B.h"

class A
{
public:
void f(B* b)
{
b->g();
}
};

我们经常会写这种代码。不是吗?
现在如果B.h文件做了修改,那么类A肯定要重新编译。最主要的是,类A直接使用了类B的方法,而类B是个具体类,假设现在有一个新的类C,它也同样实现了g(),我们想改成:
void f(C* b)
{
b->g();
}

可以看到,我们必须修改类A中函数f()的实现代码才能实现我们的目的。
但并不是什么代码都能轻易修改的。所以更好的解决办法就是利用DIP原则,让类A和类B共同依赖接口g()。只要接口g()不改变,那么我们就可以很容易的用不同的实现来代替,只要它们有相同的接口即可。

例如:
/*binterface.h*/
class binterface
{
public:
virtual void g()=0;
};

/*A.h*/
#include "binterface.h"

class A
{
public:
void f(binterface* b)
{
b->g();
}
};

现在A只依赖binterface了,只要类B和类C都实现了这个接口,那么我们就可以灵活的在类A中使用,同时不用修改类A的代码!
还有个问题,现在抽取出接口,那么这个接口从逻辑上讲到底是属于上层还是下层?(这可能会影响到文件的物理位置和包的接口)
根据专家的讲法,这个接口仍然属于下层,并不是我们会以为的上层。这个接口可以看成下层提供的一个Facade。实际的依赖关系仍然是上层依赖下层(物理依赖关系),我认为这跟DIP原则(逻辑依赖关系)并不矛盾。
我们更希望重用的是高层的策略设置模块。我们已经非常擅长通过子程序库的形式来重用低层模块。如果高层模块依赖于低层模块,那么在不同的上下文中重用高层模块将变得非常困难。然而,如果高层模块独立于低层模块, 那么高层将变得非常容易重用。 该原则是做“Framework”设计的核心。

         解决依赖这个问题的本质是使用抽象。我们让高层模块依赖于抽象(抽象类或者接口),而让低层模块来实现抽象。从原理上, 这个原则就是:
         1) 任何变量都不能拥有一个具体类的指针或者引用。
         2)任何类都不应该从具体类派生
         3)任何方法都不应该覆写基类中已经实现的方法。

         这个原则对于那些虽然具体但是却稳定的类来说似乎并不是很合适, 如果一个类不太会改变, 而且也不太可能创建其他的派生类,那么依赖它似乎并没有太大的危害。比如java的String类。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1485797

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值