敏捷软件开发第五讲-开闭原则与里氏替换原则课件

上传人:仙*** 文档编号:241387277 上传时间:2024-06-22 格式:PPTX 页数:35 大小:1.75MB
返回 下载 相关 举报
敏捷软件开发第五讲-开闭原则与里氏替换原则课件_第1页
第1页 / 共35页
敏捷软件开发第五讲-开闭原则与里氏替换原则课件_第2页
第2页 / 共35页
敏捷软件开发第五讲-开闭原则与里氏替换原则课件_第3页
第3页 / 共35页
点击查看更多>>
资源描述
广州大学华软软件学院软件工程系主讲教师:谭翔纬答疑时间:周三10:30-12:00周四9:00-12:00Tel:660028Email:第五讲:开闭原则与里氏替换原则目录目录n开放开放封封闭原原则(OCP)nOCP编程程实例例nOCP原原则实施要点施要点nLiskov替替换原原则nLiskov原原则实施要点施要点n总结开放封闭原则(OCP)什么是软件开发过程中最不稳定的因素?答案是需求!需求在软件开发过程中时时刻刻都可能发生变化。那么,如何灵活应对变化是软件结构设计中最重要也是最困难的一个问题。好的设计带来了极大了灵活性,不好的设计则充斥着僵化的臭味。所以我们要遵循开放封闭原则OCP。开放封闭原则(OCP)Bertrand Meyer,面向对象技术大师,发明了Eiffel 语言和按契约设计(Design by Contract)的思想,名著面向对象软件构造的作者,法国工程院院士。目前,他除了担任Eiffel环境和工具开发公司ISE的CTO之外,还是爱因斯坦的母校苏黎世联邦工学院计算机科学系教授,担任软件工程项目主席,同时还在澳大利亚Monash大学任教。他于1988年提出了著名的开放封闭原则(OCP)。开放封闭原则的现实意义开放封闭原则(OCP,OpenClosedPrinciple)是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合。而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的,例如后面将介绍的Liskov替换原则实现最佳的、正确的继承层次,就能保证不会违反开放封闭原则。OCP核心的思想是:软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。OCP特征软件实体(类、模块、函数等)应该是可扩展的,但是不可修改的。OCP有两大特征:l对于扩展是开放的(Openforextension)模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求。l对于更改是封闭的(Closedformodification)对模块行为扩展时,不必改动模块的源代码或二进制代码。OCP的关键在于抽象lOCP的关键在于抽象的关键在于抽象p抽象技术:abstract class,Interfacep抽象预见了可能的所有扩展(闭)p由抽象可以随时导出新的类(开)范例:手与门l如何在程序中模拟用手去开门和关门?l行为:p开门(open)p关门(close)p判断门的状态(isOpened)设计实现public class Door public class Door private boolean _isOpen=false;private boolean _isOpen=false;public boolean isOpen()public boolean isOpen()return _isOpen;return _isOpen;public void open()public void open()_isOpen=true;_isOpen=true;public void close()public void close()_isOpen=false;_isOpen=false;public class Hand public class Hand public Door door;public Door door;void do()void do()if(door.isOpen()if(door.isOpen()door.close();door.close();else else door.open();door.open();public class SmartTest public class SmartTest public static void main(String args)public static void main(String args)Hand myHand=new Hand();Hand myHand=new Hand();myHand.door=new Door();myHand.door=new Door();myHand.do();myHand.do();新的需求需要手去开关抽屉,冰箱需要手去开关抽屉,冰箱?我们只好去修改程序我们只好去修改程序!解决新的需求:修改设计public class Hand public class Hand public Door door;public Door door;public Drawer drawer;public Drawer drawer;void do(int item)void do(int item)switch(item)switch(item)case 1:case 1:if(door.isOpen()if(door.isOpen()door.close();door.close();else door.open();else door.open();break;break;case 2:case 2:if(drawer.isOpen()if(drawer.isOpen()drawer.close();drawer.close();else drawer.open();else drawer.open();break;break;public class SmartTest public class SmartTest public static void main(String args)public static void main(String args)Hand myHand=new Hand();Hand myHand=new Hand();myHand.door=new Door();myHand.door=new Door();myHand.do(1);myHand.do(1);手被改了!手被改了!主(使用手)程序也被改了!主(使用手)程序也被改了!符合OCP的设计方案public interface Excutablepublic interface Excutable public boolean public boolean isOpen();isOpen();public void open();public void open();public void close();public void close();新的实现public public class Doorclass Door implements Excutableimplements Excutable private boolean _isOpen=false;private boolean _isOpen=false;public boolean isOpen()public boolean isOpen()return _isOpen;return _isOpen;public void open()public void open()_isOpen=true;_isOpen=true;public void close()public void close()_isOpen=false;_isOpen=false;public public class Handclass Hand public Excutable item;public Excutable item;void do()void do()if(item.isOpen()if(item.isOpen()item.close();item.close();else else item.open();item.open();public public class Drawerclass Drawer implements Excutableimplements Excutable private boolean _isOpen=false;private boolean _isOpen=false;public boolean isOpen()public boolean isOpen()return _isOpen;return _isOpen;public void open()public void open()_isOpen=true;_isOpen=true;public void close()public void close()_isOpen=false;_isOpen=false;public class SmartTest public class SmartTest public static void main(String args)public static void main(String args)Hand myHand=new Hand();Hand myHand=new Hand();myHand.item=new Door();myHand.item=new Door();myHand.do();myHand.do();新的需求需要手去开关冰箱需要手去开关冰箱需要手去开关冰箱需要手去开关冰箱?为冰箱实现为冰箱实现为冰箱实现为冰箱实现ExcutableExcutable接口接口接口接口不需要修改任何原有的设计和代不需要修改任何原有的设计和代不需要修改任何原有的设计和代不需要修改任何原有的设计和代码码码码public public class Refrigeratorclass Refrigerator implements Excutableimplements Excutable private boolean _isOpen=false;private boolean _isOpen=false;public boolean isOpen()public boolean isOpen()return _isOpen;return _isOpen;public void open()public void open()_isOpen=true;_isOpen=true;public void close()public void close()_isOpen=false;_isOpen=false;OCP原则实施要点l预测变化和预测变化和“贴切的贴切的”结构结构上述的例子其实并不是完全封闭的,如果手增加了新的动作,例如搬运,很多地方还是会有改动变化。那么原来所选定的抽象对于这种变化来说反到成为一种障碍。一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。设计人员必须对于他们设计的模块应该对哪种变化封闭做出选择。必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离变化。OCP原则实施要点l要避免进行多余的抽象要避免进行多余的抽象遵循OCP的代价也是昂贵的。创建正确的抽象是要花费时间和精力的。同时这些抽象也增加了软件的复杂性。因此,开闭原则很难被完全实现,只能在某些模块、某种程度上、某个限度内符合OCP的要求。所以可以说,OCP具有理想主义的色彩,是OOD的终极目标。在项目很紧张的情况下,一般只会对能百分之百预测到的变化经行抽象,而且要是那种会经常发生变化的部分才进行抽象。OCP原则实施要点l隔离变化的手段隔离变化的手段l1.“只受一次愚弄”这意味着在我们最初编写代码时,假设变化不会发生;当变化发生时,我们就创建抽象来隔离以后发生的同类变化。l2.刺激变化。我们首先编写测试我们使用很短的迭代周期进行开发一个周期为几天而不是几周我们在加入基础结构之前就开发特性,并且经常性的把那些特性展示给涉众我们首先开发最重要的特性尽早的、经常的发布软件Liskov替换原则(LSP)lLSP(The Liskov Substitution Principle,Liskov替换原替换原则)则)p“若对于类型S的任一对象o1,均有类型T的对象o2存在,使得在T定义的所有程序P中,用o1替换o2之后,程序的行为不变,则S是T的子类型”p如果在任何情况下,子类(或子类型)或实现类与基类都是可以互换的,那么继承的使用就是合适的。为了达到这一目标,子类不能添加任何父类没有的附子类不能添加任何父类没有的附子类不能添加任何父类没有的附子类不能添加任何父类没有的附加约束加约束加约束加约束“子类对象必须可以替换父类对象子类对象必须可以替换父类对象”从问题开始!l长方形与正方形长方形与正方形p假如我们有一个类:长方形(Rectangle)p我们需要一个新的类,正方形(Square)p问:可否直接继承长方形?没问题,因为数学上正方形就是长方形的子类!没问题,因为数学上正方形就是长方形的子类!没问题,因为数学上正方形就是长方形的子类!没问题,因为数学上正方形就是长方形的子类!开始设计:正方形public class Rectangle public class Rectangle private int width;private int width;private int height;private int height;public void setWidth(int w)public void setWidth(int w)width=w;width=w;public int getWidth()public int getWidth()return width;return width;public void setHeight(int h)public void setHeight(int h)height=h;height=h;public int getHeight()public int getHeight()return height;return height;public class Square public class Square extends Rectangle extends Rectangle public void setWidth(int w)public void setWidth(int w)super.setWidth(w);super.setWidth(w);super.setHeight(w);super.setHeight(w);public void setHeight(int h)public void setHeight(int h)super.setWidth(h);super.setWidth(h);super.setHeight(h);super.setHeight(h);设计方案正确吗?public static void public static void resizeresize(Rectangle r)(Rectangle r)while(r.getHeight()=r.getWidth()while(r.getHeight()=r.getWidth()r.setHeight(r.getHeight()+1);r.setHeight(r.getHeight()+1);System.out.println(System.out.println(“It It s OK.);s OK.);Rectangle r1=new Rectangle();Rectangle r1=new Rectangle();r1.setHeight(5);r1.setHeight(5);r1.setWidth(15);r1.setWidth(15);resize(r1);resize(r1);Rectangle r2=new Square();Rectangle r2=new Square();r2.setHeight(5);r2.setHeight(5);r2.setWidth(15);r2.setWidth(15);resize(r2);resize(r2);使用父类(长方形)时,程序正常运行使用父类(长方形)时,程序正常运行使用父类(长方形)时,程序正常运行使用父类(长方形)时,程序正常运行使用子类(正方形)时,程序陷入死循环使用子类(正方形)时,程序陷入死循环使用子类(正方形)时,程序陷入死循环使用子类(正方形)时,程序陷入死循环设计出问题了?继承出问题了?设计出问题了?继承出问题了?设计出问题了?继承出问题了?设计出问题了?继承出问题了?违背LSP原则Square类针对类针对height、width添加了添加了Rectangle所没有所没有的附加的约束的附加的约束违背了违背了LSP原则原则带来潜在的设计问题(使用带来潜在的设计问题(使用resize方法时,子类出错!)方法时,子类出错!)-23-怎么办?在可能的情况下,由抽象类(接口)继承在可能的情况下,由抽象类(接口)继承在可能的情况下,由抽象类(接口)继承在可能的情况下,由抽象类(接口)继承-24-解决方案IS-A关系的思考?l鸵鸟是鸟吗?是l鸵鸟有翅膀,鸟也有翅膀l鸵鸟有喙,鸟也有喙l但是l鸟.getFlySpeed()l鸵鸟.getRunSpeed()l有着不同结论:IS-A应当是关于行为的。LSP清晰的指出,OOD中ISA关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。IS-A关系的思考(续)l对于动物学家只关心鸟的生理特征,对他们来说,鸵鸟就是鸟l对于养鸟人关心鸟的行为特征,鸵鸟不是鸟 他们都正确l考虑一个特定设计是否恰当时,不能完全孤立地看这个解决方案,应该根据设计的使用者提出的合理假设来审视。抽象类与具体类只要有可能,不要从具体类继承。只要有可能,不要从具体类继承。只要有可能,不要从具体类继承。只要有可能,不要从具体类继承。行为集中的方向是向上的(抽象行为集中的方向是向上的(抽象行为集中的方向是向上的(抽象行为集中的方向是向上的(抽象类)类)类)类)数据集中的方向是向下的(具体数据集中的方向是向下的(具体数据集中的方向是向下的(具体数据集中的方向是向下的(具体类)类)类)类)LSP原则实施要点l一个模型,如果孤立的看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来表现。l有谁知道设计的使用者会做出什么样的合理假设呢?大多数这样的假设都很难预测。事实上,如果试图去预测所有这些假设,我们所得到的系统很可能会充满不必要的复杂性的臭味。l因此,像OCP原则一样通常最好的办法就是只预测那些最明显的对于LSP的违反情况,而推迟所有其它的预测,直到出现相关的脆弱性的臭味时,才去处理它们。LSP原则实施要点l基于契约设计基于契约设计(DBC:DesignByContract)。使用DBC,类的编写者能够显式的规定针对该类的契约。客户代码的编写者可以通过该契约获悉可以依赖的行为方式。契约是通过为每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的。要使一个方法得以执行,前置条件必须要为真。执行完毕后,该方法要保证后置条件为真。LSP原则实施要点l在单元测试中指定契约也可以通过编写单元测试的方式来指定契约。客户代码编写者会去查看这些单元测试,这样他们就可以知道对于要使用的类,应该做什么合理的假设。LSP原则实施要点l启发式规则1.派生类中的退化函数在基类中实现了f()方法,在派生类中的函数f()就是退化的,派生类中的退化函数并不总表示为违反LSP,但是当存在这种情况时,还是值得注意一下的。2.从派生类中抛出异常在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把它们添加到派生类的方法中就会导致不可替换性。此时要遵循LSP,要么就必须改变使用者的期望,要么派生类就不应该抛出这些异常。LSP原则实施要点里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。下面是几种常用的遵循LSP的实施方法:l子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。l子类中可以增加自己特有的方法。l当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。l当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。总结在许多方面,OCP是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处(也就是:灵活性、可重用性以及可维护性)。然而,并不是说只要使用一种面向对象语言就是遵循了这个原则。对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。结束语当你尽了自己的最大努力时,失败也是伟大的,所以不要放弃,坚持就是正确的。When You Do Your Best,Failure Is Great,So DonT Give Up,Stick To The End感谢聆听不足之处请大家批评指导Please Criticize And Guide The Shortcomings演讲人:XXXXXX 时 间:XX年XX月XX日
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 管理文书 > 施工组织


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!