您好,欢迎访问三七文档
当前位置:首页 > 建筑/环境 > 工程监理 > 02--第二章-面向对象设计原则
第二章面向对象设计原则SRP,OCP,LSP,DIP,ISP,LoD,CARP2.1SRP:单一职责原则•这条原则曾经在TomDeMaro和MeilirPage-Jones的著作中描述过,并称之为内聚性(cohesion)。他们把内聚性定义为:一个模块的组成元素之间的功能相关性。•单一职责原则–就一个类而言,应该仅有一个引起它变化的原因。–每一个职责都是变化的一个轴线(anaxisofchange)。当需求变化时,该变化会反映为类的职责的变化,如果一个类承担了多余一个的职责,那么引起它变化的原因就会有多个。如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的(fragile)设计,当变化发生时,设计会遭受到意想不到的破坏—如:矩形类中同时包含area()和draw()。2.1SRP:单一职责原则•定义职责–在SRP中,我们把职责定义为“变化的原因”(areasonforchange)。–如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。有时,我们很难注意到这一点,我们习惯于“权力集中”,这可能是人的本性。•如:瑞士军刀,手机–另一方面,如果应用程序的变化总是导致多个职责同时变化,如果硬去分离它们反而会导致不必要的复杂性的臭味—过犹不及。•结论–SRP是所有原则中最简单的之一,也是最难正确运用的之一。我们会自然地把职责结合在一起。–软件设计真正要做的许多内容,就是发现职责并把那些职责互相分离(找到变化点,并封装之)。事实上,我们将要论述的其余原则都会以这样或那样的方式回到这个问题上。2.2OCP:开放-封闭原则•任何系统在其生命周期中都可能发生变化…•如何才能创建出在变化面前保持稳定的设计?-----什么是稳定的设计?•OCP:开放-封闭原则–软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。•具有僵化性设计臭味的软件就违反了OCP原则:程序中一处的改动会引起连锁反应,导致一系列相关模块的改动。•实际开发过程中,我们会使用各种方法尽量接近这个目标。2.2OCP:开放-封闭原则--OCP概述•(1)对于扩展是开放的–这意味着模块的行为是可扩展的。我们可以根据需求的变化来改变模块的功能•(2)对于修改是封闭的–对模块行为进行扩展时,不必改动模块的源代码或二进制代码(需要重新编译即为修改)•如何做到既不改变一个模块的源代码,又能改变模块的行为?–抽象:把一个功能的通用部分和实现细节清晰的分离开来;通过派生来扩展功能。2.2OCP:开放-封闭原则--Shape应用程序•违反OCP—1(C或C++语言实现)--shape.h---------------------------------------enumShapeType{circle,square};//图形类型structShape{ShapeTypeitsType;};--circle.h---------------------------------------//圆形structCircle{ShapeTypeitsType;doubleitsRadius;PointitsCenter;};2.2OCP:开放-封闭原则--Shape应用程序•违反OCP--2voidDrawCircle(structCircle*);//画圆动作--square.h---------------------------------------正方形structSquare{ShapeTypeitsType;doubleitsSide;PointitsTopLeft;};voidDrawSquare(structSquare*);//画正方形动作2.2OCP:开放-封闭原则--Shape应用程序•违反OCP—3--drawAllShapes.cc-------------------------------typedefstructShape*ShapePointer;//指向图形对象的指针voidDrawAllShapes(ShapePointerlist[],intn){inti;for(i=0;in;i++){structShape*s=list[i];switch(s-itsType){//必须判断图形类型,违反OCPcasesquare:DrawSquare((structSquare*)s);break;casecircle:DrawCircle((structCircle*)s);break;}}}•遵循OCP-1(C#实现)publicinterfaceShape{//抽象出一个图形类voidDraw();//包含通用的画图方法}publicclassSquare:Shape{//派生出正方形类publicvoidDraw(){//重载画图方法,画具体的正方形}}2.2OCP:开放-封闭原则--Shape应用程序•遵循OCP-2publicclassCircle:Shape{//派生出圆子类publicvoidDraw(){//重载画图方法,画具体的圆形}}//功能改动只是增加新代码,而不是更改现有的代码publicvoidDrawAllShapes(IListshapes){//画图形foreach(Shapeshapeinshapes)shape.Draw();//不用区分何种图形}2.2OCP:开放-封闭原则--Shape应用程序•预测变化和“贴切的”结构–一般而言,无论模块多么的“封闭”,都会存在一些无法对之封闭的变化。–没有对于所有的情况都贴切的模型!–预测变化需要经验,而且预测往往出错!–遵循OCP的代价也很昂贵,我们希望OCP的应用限定在可能会发生的变化上。–推荐---适当调查,提出问题,使用一般经验常识,一直等到变化发生时才采取行动。–不放置钓钩(hook),只受一次愚弄,尽早刺激变化•我们愿意被第一颗子弹击中,但是我们会确保自己不会被同一支枪发射的其他任何子弹击中。•OCP是面向对象设计的核心所在!!!2.2OCP:开放-封闭原则--Shape应用程序2.3LSP:Liskov替代原则•子类型(subtype)必须能够替换掉它们的基类型(basetype)。•只有子类能完全替代父类才能保证抽象父类的复用和扩展。只要是基类出现的地方,一定能够出现子类!LSP指导继承,是继承的基石人与马的关系(符合LSP):对于人骑马的操作,换成黑(白)马也成立;《墨子·小取》(LSP中的基类/子类位置互换则不成立)娣,美人也,爱娣,非爱美人也……盗,人也,恶盗,非恶人也违反LSP的情形:(正方形isa矩形?)企鹅是一种鸟吗?----鸟都有翅膀,而且都会飞,企鹅虽然也有翅膀,但是企鹅不会飞,那么企鹅可以继承鸟这个类吗?publicclassRectangle{privatePointtopLeft;//左上角端点坐标privatedoublewidth;privatedoubleheight;publicvirtualdoubleWidth{//宽度get{returnwidth;}set{width=value;}}publicvirtualdoubleHeight{//高度get{returnheight;}set{height=value;}}}publicclassSquare:Rectangle{publicoverridedoubleWidth{//长度/宽度一起改变set{base.Width=value;base.Height=value;}}publicoverridedoubleHeight{//长度/宽度一起改变set{base.Height=value;base.Width=value;}}while(s.Width=s.Height)s.Height++;(死循环?)继承依赖的IS-A关系是就行为方式而言的,从行为方式来说,正方形与长方形在长与宽的操作行为上不同,因此正方形不是矩形!!!基类内部的方法不能含有对子类类型的判断,换句话说,如果子类的添加/改变会导致我们改变基类,这就常常意味着设计是有缺陷的-----对于LSP的违反常常会导致以明显违反OCP的方式使用运行时类型检查(ifxxx.type==…)。2.4DIP:依赖倒置原则•依赖倒置原则(DIP)–高层模块不应该依赖于低层模块,二者都应该依赖于抽象。–抽象不应该依赖于细节,细节应该依赖于抽象。–推论:要针对接口编程,不要针对实现编程。•与传统的高层依赖于低层、策略依赖于细节相比,结构“倒置”。2.4DIP:依赖倒置原则•传统的模块依赖存在的缺点:–如果高层依赖于低层,低层变化势必引起高层发生变化。–低层和细节往往会面临激烈的变化,高层不应跟着变化;高层的变化往往来自于需求,这种策略型的的变化也不应该影响低层和细节。•谁也不要依赖谁,除了约定的接口,大家都要灵活自在!高层模块低层模块高层模块如何复用?2.4DIP:依赖倒置原则依赖倒转:•无论是高层还是低层都不互相依赖•高层模块不依赖低层模块,两者都应该依赖于抽象。高层模块低层模块Interface或抽象类2.4DIP:依赖倒置原则•倒置的接口所有权–客户拥有抽象接口,而他们的服务则从这些抽象接口派生。(谁用谁拥有,接口要求由客户提出并制定)–Don’tcallus,we’llcallyou(Hollywood原则).–低层模块实现了在高层模块中声明并被高层模块调用的接口。•依赖于抽象–程序中的所有依赖关系都应该终止于抽象类或者接口。•任何变量都不应该指向具体类;•任何类都不应该从具体类派生;•任何方法都不应该重写它的任何基类中的已经实现了的方法。–如果一个类不太会改变,并且也不会创建派生类,那么依赖于它并不会造成损害。2.4DIP:依赖倒置原则--示例•publicclassButton{privateLamplamp;//抽象没有和具体细节分离publicvoidPoll(){if(/*somecondition*/)lamp.TurnOn();//高层策略依赖于低层模块}}2.4DIP:依赖倒置原则--示例•publicclassButton{privateButtonServerlamp;//抽象和具体细节分离publicvoidPoll(){if(/*somecondition*/)lamp.TurnOn();//高层策略不依赖于低层模块}}2.5ISP:接口隔离原则•这个原则用来处理“胖接口”所存在的缺点—如果类的接口不是内聚的,就表示该类具有“胖接口”。•“胖接口”应该分解,随之而来的是类的分解,客户看到的应该是多个具有内聚接口的抽象类。2.5.1接口污染•考虑一个例子—安全门系统:publicabstractclassDoor{//安全系统中的门voidLock();voidUnlock();boolIsDoorOpen();}现考虑增加TimedDoor—如果开门时间过长,则会发出警报publicclassTimer{//定时对象,可以对注册过的其他对象提醒publicvoidRegister(inttimeout,TimerClientclient){/*code*/}}publicinterfaceTimerClient{//定时接口,时间到时该方法的实voidTimeOut();//现者将给出具体的反映行为}2.5.1接口污染•怎样将TimerClient类和TimedDoor类联系起来?•抽象类Door依赖于TimerClient,但实际上并非所有种类的Door都需要定时功能,Door类接口被一个它不需要的方法污染了.2.5.2分离客户就是分离接口•接口会影响客户操作,客户的要求也会驱使接口改变。如果要处理多次超时操作(上一次超时记录未到,关门后再开门,超时计时应该重新计算),必须在接口中加入timeOutId。publicclassTimer{publicvoidRegist
本文标题:02--第二章-面向对象设计原则
链接地址:https://www.777doc.com/doc-3743294 .html