面向对象的设计原则Object-OrientedDesignPrinciples-2-从问题开始!长方形与正方形假如我们有一个类:长方形(Rectangle)我们需要一个新的类,正方形(Square)问:可否直接继承长方形?没问题,因为数学上正方形就是长方形的子类!-3-开始设计:正方形publicclassRectangle{privateintwidth;privateintheight;publicvoidsetWidth(intw){width=w;}publicintgetWidth(){returnwidth;}publicvoidsetHeight(inth){height=h;}publicintgetHeight(){returnheight;}}publicclassSquareextendsRectangle{publicvoidsetWidth(intw){super.setWidth(w);super.setHeight(w);}publicvoidsetHeight(inth){super.setWidth(h);super.setHeight(h);}}-4-设计方案正确吗?publicstaticvoidresize(Rectangler){while(r.getHeight()=r.getWidth()){r.setHeight(r.getHeight()+1);}System.out.println(“It’sOK.);}Rectangler1=newRectangle();r1.setHeight(5);r1.setWidth(15);resize(r1);Rectangler2=newSquare();r2.setHeight(5);r2.setWidth(15);resize(r2);使用父类(长方形)时,程序正常运行使用子类(正方形)时,程序陷入死循环设计出问题了?继承出问题了?-5-为什么会出现问题?违背了面向对象的设计原则!-6-面向对象的设计原则什么是面向对象设计原则?面向对象设计原则有什么意义?是指导面向对象设计的基本指导思想是评价面向对象设计的价值观体系是设计模式的出发点和归宿-7-设计目标设计目标可扩展性(Extensibility)灵活性(Flexibility)可插入性(Pluggability)……-8-设计质量:好的设计什么是好的设计?容易理解容易修改和扩展容易复用容易实现与应用简单、紧凑、经济适用让人工作起来心情愉快的设计-9-面向对象的基本设计原则SRP:单一职责原则TheSingleResponsibilityPrincipleOCP:开放-封闭原则TheOpen-ClosePrincipleLSP:Liskov替换原则TheLiskovSubstitutionPrincipleISP:接口隔离原则TheInterfaceSegregationPrincipleDIP:依赖倒置原则TheDependencyInversionPrinciple……-10-SRPSRP(TheSingleResponsibilityPrinciple,单一职责原则)就一个类而言,应该仅有一个引起它变化的原因有关类的职责分配问题,是面向对象设计中最重要的基本原则“Acritical,fundamentalabilityinOOA/Distoskillfullyassignresponsibilitytosoftwarecomponents.”CraigLarman-11-SRP本质SRP体现了内聚性(Cohesion)内聚性:一个模块的组成元素之间的功能相关性类的职责定义为“变化的原因”,每个职责都是变化的一个轴线;当需求变化时,该变化会反映为类的职责的变化如果一个类承担了多于一个的职责,那么引起它变化的原因就会有多个-12-违反SRP的案例Rectangle类可能会因为两方面的原因而变化:计算几何方面的原因和用户界面设计方面的原因。其中只一发生变化之后,必须修改Rectangle类,而这种修改则可能导致另一个应用程序出错除此之外,违反SRP还会带来物理依赖的缺点。-13-解决方案增加新的类,使得每个类仅有一个职责-14-单一职责原则下面这个类的设计是否合理?-15-单一职责原则这个类处理的事情太多计算薪水计算税费在磁盘上读写他们自己如何将它们转化成XML或逆向转化如何用不同的报表打印这意味着:从SAX改为JDOM、将数据库从Access变成Oracle、改变税费报表的格式,都得改变Employee类-16-单一职责原则-17-LSPLSP(TheLiskovSubstitutionPrinciple,Liskov替换原则)“若对于类型S的任一对象o1,均有类型T的对象o2存在,使得在T定义的所有程序P中,用o1替换o2之后,程序的行为不变,则S是T的子类型”如果在任何情况下,子类(或子类型)或实现类与基类都是可以互换的,那么继承的使用就是合适的。为了达到这一目标,子类不能添加任何父类没有的附加约束“子类对象必须可以替换基类对象”-18-违背LSP原则Square类针对height、width添加了Rectangle所没有的附加的约束(即要求height=width),这样Square类(子类)不能完全替换Rectangle(父类)违背了LSP原则带来潜在的设计问题(使用resize方法时,子类出错!)-19-怎么办?ABABCabstract在可能的情况下,由抽象类(接口)继承-20-抽象类与具体类具体类3抽象类1abstract抽象类2abstract具体类1抽象类3abstract具体类2只要有可能,不要从具体类继承。行为集中的方向是向上的(抽象类)数据集中的方向是向下的(具体类)-21-解决方案-22-IS-A关系的思考?鸵鸟是鸟吗?是鸵鸟有翅膀,鸟也有翅膀鸵鸟有喙,鸟也有喙…但是…鸟.getFlySpeed()鸵鸟.getRunSpeed()有着不同-23-IS-A关系的思考(续)对于动物学家只关心鸟的生理特征,对他们来说,鸵鸟就是鸟对于养鸟人关心鸟的行为特征,鸵鸟不是鸟他们都正确考虑一个特定设计是否恰当时,不能完全孤立地看这个解决方案,应该根据设计的使用者提出的合理假设来审视-24-OCPOCP(TheOpen-ClosePrinciple,开放-封闭原则)软件实体(类、模块、函数等)应该是可扩展的,但是不可修改的特征:对于扩展是开放的(Openforextension)模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求对于更改是封闭的(Closedformodification)对模块行为扩展时,不必改动模块的源代码或二进制代码-25-OCP的关键在于抽象OCP的关键在于抽象抽象技术:abstractclass,Interface抽象预见了可能的所有扩展(闭)由抽象可以随时导出新的类(开)ClientServerServerClientInterfaceInterfaceClient-26-范例:手与门如何在程序中模拟用手去开门和关门?行为:开门(open)关门(close)判断门的状态(isOpened)-27-设计实现publicclassDoor{privateboolean_isOpen=false;publicbooleanisOpen(){return_isOpen;}publicvoidopen(){_isOpen=true;}publicvoidclose(){_isOpen=false;}}publicclassHand{publicDoordoor;voiddo(){if(door.isOpen())door.close();elsedoor.open();}}publicclassSmartTest{publicstaticvoidmain(String[]args){HandmyHand=newHand();myHand.door=newDoor();myHand.do();}}-28-新的需求……需要手去开关抽屉,冰箱……?我们只好去修改程序!-29-解决新的需求:修改设计publicclassHand{publicDoordoor;publicDrawerdrawer;voiddo(intitem){switch(item){case1:if(door.isOpen())door.close();elsedoor.open();break;case2:if(drawer.isOpen())drawer.close();elsedrawer.open();break;}}}publicclassSmartTest{publicstaticvoidmain(String[]args){HandmyHand=newHand();myHand.door=newDoor();myHand.do(1);}}手被改了!主(使用手)程序也被改了!-30-符合OCP的设计方案publicinterfaceExcutable{publicbooleanisOpen();publicvoidopen();publicvoidclose();}-31-新的实现publicclassDoorimplementsExcutable{privateboolean_isOpen=false;publicbooleanisOpen(){return_isOpen;}publicvoidopen(){_isOpen=true;}publicvoidclose(){_isOpen=false;}}publicclassHand{publicExcutableitem;voiddo(){if(item.isOpen())item.close();elseitem.open();}}publicclassDrawerimplementsExcutable{privateboolean_isOpen=false;publicbooleanisOpen(){return_isOpen;}publicvoidopen(){_isOpen=true;}publicvoidclose(){_isOpen=false;}}publicclassSmartTest{publicstaticvoidmain(String[]args){HandmyHand=newHand();myHand.item=newDoor();myHand.do();}}-32-publicclassRefrigeratorimplementsExcutable{privateboolean_isOpen=false;publicbooleanisOpen(){return_isOpen;}publicvoidopen(){_isOpen=true;}publicvoidclose(){_isOpen=false;}}新的需求……需要手去开关冰箱……?为冰箱实现Excutable接口不需要修改任何原有的设计和代码-33-关于OCPOCP是OOD中很多说法的核心如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及可健壮性LSP是OCP成为可能的主要原则之一正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展-34-ISPISP(TheInterfaceSegregationPrinciple,接口隔离原则)客户不应该依赖他们不用到的方法,只给每个客户它所需要的接口为了避免“肥接口(fatinterface)”,应当以一个类实现多个接口,而各客户仅仅获知必须的接口-35-接口污染需求:一扇能超时报警的门Do