版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、11种设计原则类原则1.单一职责原则 - Single Responsibility Principle(SRP) 就一个类而言,应该仅有一个引起它变化的原因。 职责即为“变化的原因”。 2.开放-封闭原则 - Open Close Principle(OCP) 软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。对于扩展是开放的,对于更改是封闭的. 关键是抽象.将一个功能的通用部分和实现细节部分清晰的分离开来。开发人员应该仅仅对程序中呈现出频繁变化的那些部分作出抽象. 拒绝不成熟的抽象和抽象本身一样重要 ) 3.里氏替换原则 - Liskov Substitution Princip
2、le(LSP) 子类型(subclass)必须能够替换掉它们的基类型(superclass)。 4.依赖倒置原则(IoCP) 或 依赖注入原则 - Dependence Inversion Principle(DIP) 抽象不应该依赖于细节。细节应该依赖于抽象。Hollywood原则: "Don't call us, we'll call you". 程序中所有的依赖关系都应该终止于抽象类和接口。针对接口而非实现编程。任何变量都不应该持有一个指向具体类的指针或引用。任何类都不应该从具体类派生。 任何方法都不应该覆写他的任何基类中的已经实现了的方法。5.接口隔
3、离原则(ISP) 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。多个面向特定用户的接口胜于一个通用接口。 包(类库、DLL)内聚原则6.重用发布等价原则(REP) 重用的粒度就是发布的粒度。 7.共同封闭原则(CCP) 包(类库、DLL)中的所有类对于同一类性质的变化应该是共同封闭的。 一个变化若对一个包产生影响, 则将对该包中的所有类产生影响, 而对于其他的包不造成任何影响。 8.共同重用原则(CRP) 一个包(类库、DLL)中的所有类应该是共同重用的。 如果重用了包(类库、DLL)中的一个类, 那么就要重用包(类库、DLL)中的所有类。 (相互之间没有紧密联系
4、的类不应该在同一个包(类库、DLL)中。) 包(类库、DLL)耦合原则9.无环依赖原则(ADP) 在包的依赖关系图中不允许存在环。 10.稳定依赖原则(SDP) 朝着稳定的方向进行依赖。 应该把封装系统高层设计的软件(比如抽象类)放进稳定的包中,不稳定的包中应该只包含那些很可能会改变的软件(比如具体类)。 11.稳定抽象原则(SAP) 包的抽象程度应该和其稳定程度一致。一个稳定的包应该也是抽象的,一个不稳定的包应该是抽象的. ) 其它扩展原则12.BBP(Black Box Principle)黑盒原则 多用类的聚合,少用类的继承。 13.DAP(Default Abstraction Pri
5、nciple)缺省抽象原则 在接口和实现接口的类之间引入一个抽象类,这个类实现了接口的大部分操作. 14.IDP(Interface Design Principle)接口设计原则 规划一个接口而不是实现一个接口。 15.DCSP(Don't Concrete Supperclass Principle) 不要构造具体的超类原则,避免维护具体的超类。 16.迪米特法则 一个类只依赖其触手可得的类。 Open-Closed Principle 软件设计中的“开-闭原则”这个原则最早是由Bertrand Meyer提出,英文的原文是:Software entitie
6、s should be open for extension,but closed for modification.意思是说,一个软件实体应当对扩展开放,对修改关闭.也就是说,我们在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,换句话说就是,应当可以在不必修改源代码的情况下改变这个模块的行为. 满足OCP的设计给系统带来两个无可比拟的优越性.1.通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性. 2.已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性接口隔离原则isp一个类
7、对另一个类的依赖应该表现成依赖尽可能小的接口。 这个原则是用来处理胖接口的缺陷,避免接口承担太多的责任。比如说一个接口内的方法可以被分成好几组,分别为不同的客户程序服务,说明这个接口太胖了。当然,确实也有一些类不需要内聚的接口,但这些类不应该做为单独的类被客户程序直接看到,而应该通过抽象基类或接口来关联访问。 接口污染 所谓接口污染就是为接口添加了不必要的职责。在接口中加一个新方法只是为了给实现类带来好处,以减少类的数目。持续这样做,接口就被不断污染,变胖。实际上,类的数目根本不是什么问题,接口污染会带来维护和重用方面的问题。最常见的问题是我们为了重用被污染的接口,被迫实现并维护不必要的方法。
8、 分离客户程序就是分离接口。如果客户程序是分离的,那么相应的接口也应该是分离的,因为客户程序对它们使用的接口有反作用力。通常接口发生了变化,我们就要考虑所有使用接口的客户程序该如何变化以适应接口的变化。如果客户程序发生了变化呢?这时也要考虑接口是否需要发生变化,这就是反作用力。有时业务规则的变化不是那么直接的,而是通过客户程序的变化引发的,这时我们就需要改变接口以满足客户程序的需要。 分离接口的方式一般分为两种,委托和多继承。前者把请求委托给别的接口的实现类来完成需要的职责,后者则是通过实现多个接口来完成需要的职责。两种方式各有优缺点,通常我们应该先考虑后一个方案,如果涉及到类型转换时则选择前
9、一个方案。 胖接口会导致客户程序之间产生不必要的耦合关系,牵一发而动全身。分解胖接口,使客户程序只依赖它需要的方法,从设计上讲,简单易维护,重用度也高。 写出漂亮代码的七种方法首先我想说明我本文阐述的是纯粹从美学的角度来写出代码,而非技术、逻辑等。以下为写出漂亮代码的七种方法:1, 尽快结束 if语句例如下面这个JavaScript语句,看起来就很恐怖:1 function findShape(flags, point, attribute, list) 2 if(!findShapePoints(flags, point, attribute) 3
10、0; if(!doFindShapePoints(flags, point, attribute) 4 if(!findInShape(flags, point, attribute) 5 if(!findFromG
11、uide(flags,point) 6 if(list.count() > 0 && flags = 1) 7
12、 doSomething();8 9 10
13、60; 11 12 13 但如果这么写就好看得多:1 function findShape(flags, point, attribute, list) 2 if(findShapePoints(flags, point, attribute) 3 return;4 56
14、160; if(doFindShapePoints(flags, point, attribute) 7 return;8 910 if(findInShape(flags, point, attribute) 11 return;12 1314 if(findF
15、romGuide(flags,point) 15 return;16 1718 if (!(list.count() > 0 && flags = 1) 19 return;20 2122 doSomething();2324 你可能会很不喜欢第二种的表述方式,但反
16、映出了迅速返回if值的思想,也可以理解为:避免不必要的else陈述。2, 如果只是简单的布尔运算(逻辑运算),不要使用if语句例如:1 function isStringEmpty(str)2 if(str = "") 3 return true;4 5 else 6 return false;7
17、60; 8 可以写为:1 function isStringEmpty(str)2 return (str = "");3 3, 使用空白,这是免费的例如:1 function getSomeAngle() 2 / Some code here then3 radAngle1 = Math.atan(slope(center, point1);4 radAngle2 = Math.atan(slope(cen
18、ter, point2);5 firstAngle = getStartAngle(radAngle1, point1, center);6 secondAngle = getStartAngle(radAngle2, point2, center);7 radAngle1 = degreesToRadians(firstAngle);8 radAngle2 = degreesToRadians(secondAngle);9
19、160; baseRadius = distance(point, center);10 radius = baseRadius + (lines * y);11 p1"x" = roundValue(radius * Math.cos(radAngle1) + center"x");12 p1"y" = roundValue(radius * Math.sin(radAngle1) + center"y")
20、;13 pt2"x" = roundValue(radius * Math.cos(radAngle2) + center"y");14 pt2"y" = roundValue(radius * Math.sin(radAngle2) + center"y");15 / Now some more code16 很多开发者不愿意使用空白,就好像这要收费一样。我在此并非刻意地添加空白,粗鲁地打断代码的连贯性。在
21、实际编写代码的过程中,会很容易地发现在什么地方加入空白,这不但美观而且让读者易懂,如下:1 function getSomeAngle() 2 / Some code here then3 radAngle1 = Math.atan(slope(center, point1);4 radAngle2 = Math.atan(slope(center, point2);56 firstAngle = getStartAngle(radAngle1, po
22、int1, center);7 secondAngle = getStartAngle(radAngle2, point2, center);89 radAngle1 = degreesToRadians(firstAngle);10 radAngle2 = degreesToRadians(secondAngle);1112 baseRadius = distance(point, center);13 radiu
23、s = baseRadius + (lines * y);1415 p1"x" = roundValue(radius * Math.cos(radAngle1) + center"x");16 p1"y" = roundValue(radius * Math.sin(radAngle1) + center"y");1718 pt2"x" = roundValue(radius * Mat
24、h.cos(radAngle2) + center"y");19 pt2"y" = roundValue(radius * Math.sin(radAngle2) + center"y");20 / Now some more code21 4, 不要使用无谓的注释无谓的注释让人费神,这实在很讨厌。不要标出很明显的注释。在以下的例子中,每个人都知道代码表达的是“students id”,因而没必要标出。1 function existsStudent(id, li
25、st) 2 for(i = 0; i < list.length; i+) 3 student = listi;45 / Get the student's id6 thisId = student.getId();78 if(thisId = id) 9
26、60; return true;10 11 12 return false; 13 5, 不要在源文件中留下已经删除的代码,哪怕你标注了如果你使用了版本控制,那么你就可以轻松地找回前一个版本的代码。如果别人大费周折地读了你的代码,却发现是要删除的代码,这实在太恨人了。/function thisReallyHandyFunc
27、tion() / someMagic();/ someMoreMagic();/ magicNumber = evenMoreMagic();/ return magicNumber;/6,不要有太长的代码看太长的代码实在太费劲,尤其是代码本身的功能又很小。如下:1 public static EnumMap<Category, IntPair>
28、 getGroupCategoryDistribution(EnumMap<Category, Integer> sizes, int groups) 2 EnumMap<Category, IntPair> categoryGroupCounts = new EnumMap<Category,IntPair>(Category.class);34 for(Category cat : Cat
29、egory.values() 5 categoryGroupCounts.put(cat, getCategoryDistribution(sizes.get(cat), groups);6 #我并不是说非要坚持70个字符以内,但是一个比较理想的长度是控制在120个字符内。如果你把代码发布在互联网上,用户读起来就很困难。7,不要在一个功能(或者函数内)有太多代码行我的一个老同事曾经
30、说Visual C+很臭,因为它不允许你在一个函数内拥有超过10,000行代码。我记不清代码行数的上限,不知道他说的是否正确,但我很不赞成他的观点。如果一个函数超过了50行,看起来有多费劲你知道么,还有没完没了的if循环,而且你还的滚动鼠标前后对照这段代码。对我而言,超过35行的代码理解起来就很困难了。我的建议是超过这个数字就把一个函数代码分割成两个。类的设计原则 2009-12-23 来源: 接口定义接口定义开闭原则Software entities (classes, modules, function, etc.) should be open for extensi
31、on, but closed for modification.软件实体(模块,类,方法等)应该对扩展开放,对修改关闭。 开闭原则(OCP:Open-Closed Principle)是指在进行面向对象设计(OOD:Object Oriented Design)中,设计类或其他程序单位时,应该遵循:- 对扩展开放(open)- 对修改关闭(closed)的设计原则。开闭原则是判断面向对象设计是否正确的最基本的原理之一。根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。- 扩展开放:某模块的功能是可扩展的,则该模块是
32、扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。- 修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改关闭的。这也是系统设计需要遵循开闭原则的原因:1)稳定性。开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。2)扩展性。开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。开闭原则的实现方法为了满足开闭原则的 对修改关闭(closed for modification) 原则以及扩展开放(op
33、en for extension) 原则,应该对软件系统中的不变的部分加以抽象,在面向对象的设计中,- 可以把这些不变的部分加以抽象成不变的接口,这些不变的接口可以应对未来的扩展;- 接口的最小功能设计原则。根据这个原则,原有的接口要么可以应对未来的扩展;不足的部分可以通过定义新的接口来实现;- 模块之间的调用通过抽象接口进行,这样即使实现层发生变化,也无需修改调用方的代码。接口可以被复用,但接口的实现却不一定能被复用。接口是稳定的,关闭的,但接口的实现是可变的,开放的。可以通过对接口的不同实现以及类的继承行为等为系统增加新的或改变系统原来的功能,实现软件系统的柔软扩展。简单地说,软件系统是否
34、有良好的接口(抽象)设计是判断软件系统是否满足开闭原则的一种重要的判断基准。现在多把开闭原则等同于面向接口的软件设计。开闭原则的相对性软件系统的构建是一个需要不断重构的过程,在这个过程中,模块的功能抽象,模块与模块间的关系,都不会从一开始就非常清晰明了,所以构建100%满足开闭原则的软件系统是相当困难的,这就是开闭原则的相对性。但在设计过程中,通过对模块功能的抽象(接口定义),模块之间的关系的抽象(通过接口调用),抽象与实现的分离(面向接口的程序设计)等,可以尽量接近满足开闭原则。单一职责原则前言Robert C. Martin氏为我们总结了在面向对象的设计(OOD)中应该遵循的原则,这些原则
35、被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的单一职责原则:Single Responsibility Principle (SRP)。可以从这里查看Single Responsibility Principle (SRP)的原文。概要There should never be more than one reason for a class to change.永远不要让一个类存在多个改变的理由。换句话说,如果一个类需要改变,改变它的理由永远只有一个。如
36、果存在多个改变它的理由,就需要重新设计该类。SRP(Single Responsibility Principle)原则的核心含意是:只能让一个类有且仅有一个职责。这也是单一职责原则的命名含义。为什么一个类不能有多于一个以上的职责呢?如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,而这种变化将影响到该类不同职责的使用者(不同用户):1,一方面,如果一个职责使用了外部类库,则使用另外一个职责的用户却也不得不包含这个未被使用的外部类库。2,另一方面,某个用户由于某个原因需要修改其中一个职责,另外一个职责的用户也将受到影响,他将不得不重新编译和配置。这违反了设计的开闭原则,也不是
37、我们所期望的。职责的划分既然一个类不能有多个职责,那么怎么划分职责呢?Robert.C Martin给出了一个著名的定义:所谓一个类的一个职责是指引起该类变化的一个原因。If you can think of more than one motive for changing a class, then that class has more than one responsibility.如果你能想到一个类存在多个使其改变的原因,那么这个类就存在多个职责。Single Responsibility Principle (SRP)的原文里举了一个Modem的例子来说明怎么样进行职责的划分,这里
38、我们也沿用这个例子来说明一下:SRP违反例:public interface Modem public void dial(String pno); /拨号 public void hangup(); /挂断 public void send(char c); /发送数据&
39、#160; public char recv(); /接收数据咋一看,这是一个没有任何问题的接口设计。但事实上,这个接口包含了2个职责:第一个是连接管理(dial, hangup);另一个是数据通信(send, recv)。很多情况下,这2个职责没有任何共通的部分,它们因为不同的理由而改变,被不同部分的程序调用。所以它违反了SRP原则。下面的类图将它的2个不同职责分成2个不同的接口,这样至少可以让客户端应用程序使用具有单一职责的接口:接口定义具体类实现让ModemIm
40、plementation实现这两个接口。我们注意到,ModemImplementation又组合了2个职责,这不是我们希望的,但有时这又是必须的。通常由于某些原因,迫使我们不得不绑定多个职责到一个类中,但我们至少可以通过接口的分割来分离应用程序关心的概念。事实上,这个例子一个更好的设计应该是这样的,如图:具体类实现接口定义小结Single Responsibility Principle (SRP)从职责(改变理由)的侧面上为我们对类(接口)的抽象的颗粒度建立了判断基准:在为系统设计类(接口)的时候应该保证它们的单一职责性。接口分隔原则前言Robert C. Martin氏为我们总结了在面向对
41、象的设计(OOD)中应该遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的接口分隔原则:Interface Segregation Principle (ISP)。可以从这里查看Interface Segregation Principle (ISP)的原文。概要Clients should not be forced to depend upon interfaces that they do not use.不能强迫用户去依赖那些他们不
42、使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。它包含了2层意思:- 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。- 接口的依赖(继承)原则:如果一个接口a依赖(继承)另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应该遵循上述原则:不应该包含用户不使用的方法。反之,则说明接口a被b给污染了,应该重新设计它们的关系。如果用户被迫依赖他们不使用的接口,当接口发生改变时,他们也不得不跟着改变。换而言之,一个用户依赖了未使用但被
43、其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。下面我们举例说明怎么设计接口或类之间的关系,使其不违反ISP原则。假如有一个Door,有lock,unlock功能,另外,可以在Door上安装一个Alarm而使其具有报警功能。用户可以选择一般的Door,也可以选择具有报警功能的Door。有以下几种设计方法:ISP原则的违反例:方法一:在Door接口里定义所有的方法。图:但这样一来,依赖Door接口的CommonDoor却不得不实现未使用的alarm()方法。违反了ISP原则。方法二:在Alarm接口定义alarm方法,在Do
44、or接口定义lock,unlock方法,Door接口继承Alarm接口。跟方法一一样,依赖Door接口的CommonDoor却不得不实现未使用的alarm()方法。违反了ISP原则。遵循ISP原则的例:方法三:通过多重继承实现在Alarm接口定义alarm方法,在Door接口定义lock,unlock方法。接口之间无继承关系。CommonDoor实现Door接口,AlarmDoor有2种实现方案:1),同时实现Door和Alarm接口。2),继承CommonDoor,并实现Alarm接口。该方案是继承方式的Adapter设计模式的实现。第2)种方案更具有实用性。这种设计遵循了ISP设计原则。方
45、法四:通过委让实现这种方法其实是委让方式的Adapter设计模式的实现。在这种方法里,AlarmDoor实现了Alarm接口,同时把功能lock和unlock委让给CommonDoor对象完成。这种设计遵循了ISP设计原则。小结Interface Segregation Principle (ISP)从对接口的使用上为我们对接口抽象的颗粒度建立了判断基准:在为系统设计接口的时候,使用多个专门的接口代替单一的胖接口。依赖倒置原则Robert C. Martin氏为我们总结了在面向对象的设计(OOD)中应该遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles
46、 of OOD”的相关文章可以从Object Menter得到。本文介绍DIP:Dependency Inversion Principle - 依赖倒置原则。有关Dependency Inversion Principle (DIP) 原文可以从这里 得到。该文提出了依赖倒置原则的2个重要方针:A. High level modules should not depend upon low level modules. Both should depend upon abstractions.B. Abstractions should not depend upon details. De
47、tails should depend upon abstractions.中文意思为:A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象B. 抽象不应该依赖于细节,细节应该依赖于抽象概念解说:依赖:在程序设计中,如果一个模块a使用/调用了另一个模块b,我们称模块a依赖模块b。高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于低层次的类,这些类我们称之为高层模块。为什么叫做依赖倒置(Dependency Inversion)呢?面向对象程序设计相对于面向过程(
48、结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。问题的提出:Robert C. Martin氏在原文中给出了“Bad Design”的定义:1. It is hard to change because every change affects too many other parts of the system.(Rigidity)系统很难改变,因为每个改变都会影响其他很多部分。2. When you make a change, unexpected parts of the system break. (Fragility)当你对某地方做一
49、修改,系统的看似无关的其他部分都不工作了。3. It is hard to reuse in another application because it cannot be disentangled fromthe current application. (Immobility)系统很难被另外一个应用重用,因为你很难将要重用的部分从系统中分离开来。导致“Bad Design”的很大原因是“高层模块”过分依赖“低层模块”。一个良好的设计应该是系统的每一部分都是可替换的。如果“高层模块”过分依赖“低层模块”,一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难
50、可以重用。比如,一个Copy模块,需要把来自Keyboard的输入复制到Print,即使对Keyboard和Print的封装已经做得非常好,但如果Copy模块里直接使用Keyboard与Print,Copy任很难被其他应用环境(比如需要输出到磁盘时)重用。问题的解决:为了解决上述问题,Robert C. Martin氏提出了OO设计的Dependency Inversion Principle (DIP) 原则。DIP给出了一个解决方案:在高层模块与低层模块之间,引入一个抽象接口层。High Level Classes(高层模块) -> Abstraction Layer(抽象接口层)
51、-> Low Level Classes(低层模块)抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。这样,高层模块不直接依赖低层模块,高层模块与低层模块都依赖抽象接口层。当然,抽象也不依赖低层模块的实现细节,低层模块依赖(继承或实现)抽象定义。Robert C. Martin氏给出的DIP方案的类的结构图:PolicyLayer->MechanismInterface(abstract)-MechanismLayer->UtilityInterface(abstract)-UtilityLayer类与类之间都通过Abstract Layer来组合关系。里氏替换原则R
52、obert C. Martin氏为我们总结了在面向对象的设计(OOD)中应该遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的里氏替换原则:Liskov Substitution Principle (LSP)。可以从这里查看Liskov Substitution Principle (LSP)的原文 。里氏替换原则LSP的概念解说Functions that use pointers or references to base class
53、es must be able to use objects of derived classes without knowing it.所有引用基类的地方必须能透明地使用其子类的对象。也就是说,只有满足以下2个条件的OO设计才可被认为是满足了LSP原则:- 不应该在代码中出现if/else之类对子类类型进行判断的条件。以下代码就违反了LSP定义。if (obj typeof Class1) do something else if (obj typeof Class2) do something else- 子类应当可以替换父类并出现在父类能够出现的任何地方,或者说如果我们把代码中使用基类的
54、地方用它的子类所代替,代码还能正常工作。里氏替换原则LSP是使代码符合开闭原则的一个重要保证。同时LSP体现了:- 类的继承原则:如果一个继承类的对象可能会在基类出现的地方出现运行错误,则该子类不应该从该基类继承,或者说,应该重新设计它们之间的关系。- 动作正确性保证:从另一个侧面上保证了符合LSP设计原则的类的扩展不会给已有的系统引入新的错误。类的继承原则:Robert C. Martin氏在介绍Liskov Substitution Principle (LSP)的原文里,举了Rectangle和Square的例子。这里沿用这个例子,但用Java语言对其加以重写,并忽略了某些细节只列出下面
55、的精要部分来说明 里氏替换原则 对类的继承上的约束。代码:这里Rectangle是基类,Square从Rectangle继承。这种继承关系有什么问题吗?假如已有的系统中存在以下既有的业务逻辑代码:void g(Rectangle r) r.setWidth(5);r.setHeight(4);if (r.getWidth() * r.getHeight() != 20) throw new RuntimeException();则对应于扩展类Square,在调用既有业务逻辑时:Rectangle square = new Square();g(square);时会抛出一个RuntimeExce
56、ption异常。这显然违反了LSP原则。动作正确性保证:因为LSP对子类的约束,所以为已存在的类做扩展构造一个新的子类时,根据LSP的定义,不会给已有的系统引入新的错误。Design by Contract根据Bertrand Meyer氏提出的Design by Contract(DBC:基于合同的设计)概念的描述,对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;同时后续条件用来说明这个方法完成时的状态,如果一个方法的执行会导致这个方法的后续条件不成立,那么这个方法也不应该正常返回。现在把前提条件以及后续
57、条件应用到继承子类中,子类方法应该满足:1)前提条件不强于基类2)后续条件不弱于基类换句话说,通过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。因此继承类不得要求用户提供比基类方法要求的更强的前提条件,亦即,继承类方法必须接受任何基类方法能接受的任何条件(参数)。同样,继承类必须顺从基类的所有后续条件,亦即,继承类方法的行为和输出不得违反由基类建立起来的任何约束,不能让用户对继承类方法的输出感到困惑。这样,我们就有了基于合同的LSP,基于合同的LSP是LSP的一种强化。在很多情况下,在设计初期我们类之间的关系不是很明确,LSP则给了我们一个判断和设计类之间关系的基准:需不需要继
58、承,以及怎样设计继承关系。针对接口编程,而不是针对实现编程 在面向对象设计方法中有很多值得提倡的方法,这些方法可以为我们的设计带来很大的灵活性,可复用性。其中一个原则就是“针对接口编程,而不是针对实现编程” 这个原则带来的好处有以下几点: Client不必知道其使用对象的具体所属类。 Client无需知道特定类,只需知道他们所期望的接口。 一个对象可以很容易地被(实现了相同接口的)的另一个对象所替换。 对象间的连接不必硬绑定(hardwire)到一个具体类的对象上,因此增加了灵活性。 松散藕合(loosens
59、coupling)。 增加了重用的可能性。提高了(对象)组合的机率,因为被包含对象可以是任何实现了一个指定接口的类。 但从辩证法的角度看,事物总有利有弊。“针对接口编程”有如上诸多好处,却不可避免的带来设计的复杂性。特别对于没有丰富经验的设计人员。 其中令我比较困惑的地方是: 要想针对接口编程,就必然要最大化接口类,使包括所有子类的方法,这样我们才能利用多态性用接口类来实现操作子类。但这会带来以下几点不足。 违反面向对象的另一个原则,这个原则是:一个类只能定义那些对它的子类有意义的操作。 接口类包括了并不是对每一个子类都有意义的方法,使接口类臃肿,难以
60、理解。 从父类继承的无用方法,如何处理。 5 Principles for OOD/OOP2008-10-21 12:501.SRP-单一责任原则2.OCP-开闭原则3.LSP-Liskov替换原则4.DIP-依赖倒置原则5.ISP-接口分离原则敏捷开发提倡简单设计的实践,“并在实现新需求时抓住机会改进设计”以对同类性质的改动封闭,做到由需求的变化驱动设计的进化(我们不能因为设计的退化而责怪需求的变化),同时经验在此起到十分重要的作用,如有经验的设计人员可以凭经验在初始设计时做出必要的抽象来满足ocp原则等,或是在需求变动时确定系统所需的抽象(所需的封闭),当然应及早
61、的刺激这种变化的出现(如测试驱动的开发方法)。OOD承诺了一系列的好处(灵活性可重用性可维护性),用OO语言设计开发,若要方便的得到这些所谓的好处,有一系列的原则是要遵循的,如SRP,OCP,LSP,ISP等。 SRP(单一职责原则)维护类的简单性,类不应承担一个以上令其变化的原因,否则应考虑分离并重新构造类,但如果的应用的变化方式总是导致类中的职责同时变化,却没必要分离他们Ocp(开闭原则)使OO系统做到对扩展开放,对修改封闭。OCP的遵循关键在于抽象,其主要实现方式有:定义接口描绘所需的操作,client只需关注接口的调用,子类型可以以任何其选择的方式实现接口,即所谓的stategy模式,
62、或者定义抽象类并于其中实现公共操作,个性操作定义为abstract或virtual,由子类型负责个性化实现,通过此两法,将功能的通用部分和实现细节分离出来。当然,设计人员应该确定(猜测或凭经验)系统对哪种变化做到封闭,因为不可能对所有变化做到封闭,如敏捷模式实践中提到shape类型排序处理问题,为做到对排序安排(或变动)的封闭(使得各子类型间无需相互知晓,也可以做到自由安排排序顺序),选择使用“数据驱动”的方式(即单独构造结构表示排序安排其中以子类类型在结构中的排列位置表先后),于shape基类中实现一次Precedes操作即可,子类型无需分别实现。OCP作为OOD核心所在依赖抽象来实现,但敏
63、捷设计(或者说好的设计)拒绝不成熟的抽象,程序仅应对频繁变化的部分做出抽象。Liskov替换原则是使得ocp成为可能的原则之一,强调“子类型subtype必须能够替换掉它们的基类型basetype”,控制OO的继承关系安排,在OOD用is-a来确定类间的继承关系,LSP指出这种is-a关系是就行为方式(即类的各操作)而言的,而行为方式是可以合理假设的,是客户程序所依赖的。为遵循LSP,可借用DBC(design by contract) 的操作前置条件和后置条件,“要使操作得以执行,前置条件必须为真,执行完毕后,该操作要确保后置条件为真”(为每个方法注明其前置和后置条件十分有帮 助),如此,则
64、“在重新声明派生类中操作时,只能使用相等或更弱的前置条件来替换原始的前置条件,只能用相等或更强的后置条件来替换原始的后置条件”(interface和其实现类间 抽象方法和其实现 此二者一定满足前述条件)。同时亦可用前述ocp遵循所用的二模式使设计符合LSP,另外子类型中的异常抛出应考虑在遵循LSP的范围内。关于提取公共部分的设计工具:“提取公共职责放入超类中,稍后添加的新的子类型可能会以新的方式支持同样的职责,此时原来的超类可能会是一个抽象类”。 DIP(依赖倒置原则),作为framework的设计核心,其相对于传统软件设计而言,通常(传统)软件设计中采用结
65、构化设计用高层模块直接调用底层模块,这样高层模块将严重依赖于底层模块的变动,在OOD中通过为高层模块定义所需使用的服务接口,底层模块现实这样的接口,高层模块通过抽象接口使用下一层(strategy模式所声明的),如此看来接口的拥有者一般是其使用者而非其实现者。通常为了满足DIP-良好OOD的基本底层机制,我么需要找出系统中潜在的抽象,而抽象通常是那些不随具体细节变化而变化的东东。 ISP接口隔离原则,如SRP维护类的简单性一样,ISP用于维护接口的简单和必要性,因为接口是为客户调用的,因此其应该是“大小尺寸合适的”,“胖”接口显然对调用者造成累赘,ISP则用
66、于将“胖”接口分离成多个合适的接口。 当然,在系统设计实现中要做到这些并非容易,单单知道其存在未必做得到将其实现到系统中,开发经验的积累同样重要,但早些知道存在个意识并在做时将其考虑进去,也是积累,慢慢来吧,n多事要做呐。架构师之路(5)-面向对象的设计原则 王泽宾 1 OO的设计原则采用面向对象的分析和设计思想,为我们分析和解决问题提供了一种全新的思维方式。我们在拿到需求之后(略去OOA,以后补全),接下来的问题就是:如何对系统进行面向对象的设计呢?按照软件工程的理论,面向对象的设计要解决的核心问题就是可维护性和可复用性。尤其是可维护性,它是影响软件生命周期重要因素,通常情况下,软件的维护成本远远大于
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 盐斤收放保管工专项知识考试复习题库(附答案)
- 卫生专业技术资格考试儿科学(中级332)专业实践能力复习难点精析
- (初中)自主招生笔试题广东省深圳市备考难点详解
- 电工电子技术课件 4-1低压元器件
- 部编版五年级上册语文全套预习单(8单元)
- 农业种植项目农业科技成果转化与应用协议
- 线上广告2026年销售推广合作协议
- 销售策略2026年咨询协议
- 线上线下重要合作项目合同
- 混合云数据迁移与同步服务合同
- 煤矸石充填塌陷区复垦技术规程-编制说明
- Python少儿编程全套教学课件
- 国电南瑞员工手册
- 小学生女生健康教育课件
- 2023硅铁多元素含量的测定电感耦合等离子体原子发射光谱法
- 三江能源有限公司煤矿矿山地质环境保护与土地复垦方案
- 关于腹腔镜胆囊切除手术的护理配合
- 重体力劳动评估程序(RBA健康安全)
- GB/T 7702.3-1997煤质颗粒活性炭试验方法强度的测定
- GB/T 21380-2008行人反光标识夜间光度性能及测试方法
- 中国药典2005版一部
评论
0/150
提交评论