- 浏览: 236935 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
Navee:
抄来抄去,还是没有实质解决问题
Ext 树.叶子查找与非叶子查找 -
haizhiguang:
我按照你的代码自己写了一个试试,没看出我的代码跟你的有什么区别 ...
JAVA 鼠标画直线举行椭圆 -
WHW1984:
整个工程目录结构是咋样的 兄弟
Jetty -- 安全认证 -- 三种配置方法 -
shixianwei:
...
Jetty Oracle DataSource Config(Jetty Oracle 数据源配置) -
cpusoft:
http://cuiyingfeng.blog.ccidnet ...
JNI(java 调用 本地接口)Tomcat的JNI库加载问题解决办法
粒度:包的内聚性原则
1) 重用发布等价原则(The Release Reuse Equivalency Principle (REP))
* 重用的粒度就是发布的粒度
* 一个可重用的包必须为发布跟踪系统所管理,使我们在新版本发布后我们还可以继续使用老版本
* 一个包中的所有类对于同一类用户来讲都应该是可重用的。
2) 共同重用原则(The Common Reuse Principle (CRP))
* 一个包中的所有类应该是共同重用的,如果重用了包中的一个类,就应该重用包中的所有类。
* 一般来说,可重用的类需要与作为该可重用抽象一部份的其它类协作,CRP规定了这些类应该属于同一个包。
* 放入同一包中的所有类应该是不可分开的,其它包仅仅依赖于其中一部份情况是不可能的(不允许的),否则,我们将要进行不必要的重新验证与重新发布,并且会白费相当数量的努力。(一个包依赖于另外一个包, 哪怕只是依赖于其中的一个类也不会削弱其依赖关系)
* CRP倾向于把包做的尽可能的小
3) 共同封闭原则(The Common Closure Principle (CCP))
* 包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中所有类产生影响,而对于其他的包不造成任何影响。
* 这是单一职责原则对于包的重新规定。
* CCP鼓励我们把可能由于同样的原因而更改的所有类共同聚集在同一个地方。将变化限制在最小数据的包中。
* CCP倾向于将包做的尽可能的大。
* CCP有益于维护者(包的作者),而REP和CRP有益于重用者(包的使用者)。
稳定性:包的耦合性原则
4) 无环依赖原则(The Acyclic Dependencies Principle (ADP))
* 在包的依赖关系图中不允许存在环。
* 包的依赖关系图应该是一个有向无环图(DAG(Directed Acyclic Grphic))
* 存在环的系统,很难确定包的构建顺序,事实上,并不存在恰当的构建顺序。
* 打破环的第一个方法:依赖倒置原则,使一个包不再依赖于另一个包,而只是依赖于其抽象接口。
* 打破环的第二个方法: 创建一个新包来包含公共依赖部份。
5) 稳定依赖原则(The Stable Dependencies Principle (SDP))
* 朝着的稳定的方向进行依赖
* 你设计了一个易于更改的包, 其它人只要创建一个对它的依赖就可以使它变的难以更改,这就是软件的反常特性。通过遵循SDP可以避免这种情况。
* 不稳定性度量:I = Ce / (Ca + Ce). Ca: Afferent Coupling. Ce: Efferent Coupling
* SDP规定一个包的I度量值应该大于它所依赖的包的I的度量值,也就是说,I的度量值应该顺着依赖的方向减少。
6) 稳定抽象原则(The Stable Abstractions Principle (SAP))
* 包的抽象程度应该和其稳定程度一致。
* 一个稳定的包同时应该是抽象的,这样,其稳定性就不会导致其无法扩展。一个不稳定的包应该是具体的,这样,因为其不稳定性使得其内部的具体代码易于修改。
* 抽象性度量:A = Na / Nc Na: Number of classes. Nc:Number of abstract classes.
* 创建一个以A为纵轴,I为横轴的坐标图,最稳定,最抽象的包位于左上角(0,1)处, 那些最不稳定,最具体的包位于右下角(1,0)处。
包的设计原则
没有学习这个技术的时候,我对于包的设计都是一种主观的行为,我认为类应该天然在一起的就放入一个包中。没有任何的度量和指导原则。当我看到这些原则的时候,发现自己真的是井底之蛙。。。
对于包的设计一般需要解决四个问题。
1,包中类分配的依据是什么?
2,使用什么原则管理包间的关系?
3,包德设计应该先于类的设计还是相反?
4,包创建后应当用于何种目的?
解决以上及其他问题的六条指导原则
包的设计核心就是低耦合高内聚的,和类的设计核心很相像
第一类(包的内聚原则)
1,重用发布等价原则 REP
重用的粒度就是发布的粒度。当一个包的目的是为了重用,那么这个包下的所有类都应该是重用的,潜在涵义就是,一个包中的类的目标是一致的,而且对于重用应该是按照重用用户类区分的,对于一类重用用户分一个包。
2,共同重用原则 CRP
当一个包被重用的时候,旗下类都会被重用。如果重用类包中一个类那么其下所有类都应该被重用。没有紧密关系的类不应该放在同一个包中。
3,共同封闭原则 CCP
包中所有类对于同一种变化应该是共同封闭的,即每一包只对应一类变化,在系统中,一类变化只影响一个包。由同一变化影响的类放在同一个包中。
第二类(包德耦合原则)
4,无环依赖原则 ADP
首先把包依赖关系影射成一个有向图,看看是否有环路存在。
如果存在环路,那么存在环路的包在更改的时候会影响很多系统包,那么包就需要重新的设计。
解除环路的方法:
(1)使用抽象应用DIP(依赖倒置原则)进行包德隔离。
(2)创建一个新包把存在环路的类放在这个新包中。
5,稳定依赖原则 SDP
朝着稳定的方向进行依赖
稳定类的概念:这个包被很多类依赖但是他不依赖任何包。那么这样的包就是一个稳定包。
稳定性的度量:
I(稳定性)=依赖别的包的数量(出度)/依赖别的包的数量(出度)+被依赖的包的数量(入度)
一个稳定的系统其上层类应该是不稳定的,下层实现应该稳定地,但是上层类还应该灵活易于扩展并能够经受住变化,应该怎么办呢?答案就是抽象类(经典!!),这样一来上层类就是稳定的,而且可以经受变化的类。
重构的方法:DIP
6,稳定抽象原则 SAP
包如果是稳定的那么应该是抽象的,包是不稳定的那么这个包中的类就应该是细节实现类。
稳定意味着抽象
稳定性的度量:
A(稳定性)=包中抽象的类的个数/包中类的总数
SAP是对于SDP的一个升华,一个扩展。也是对于抽象使用的指导原则。
关于度量的使用
通常对于包的设计我们开始的时候把我们认为应该在一起的类放在一个包中,当初步设计结束后,我们就需要通过一张图表来解决包间的问题
我们以I(稳定性)为横轴,以A(抽象性)为纵轴,连接(1,0)(0,1)成直线。这条直线就是期望线,在这条线上的类是即稳定又灵活的。对于趋向(0,0)和(1,1)的类是需要我们进行重构的,特别是趋向(0,0)的类一般都是糟糕的设计。
D‘(类到期望线的距离) = /A+I-1/
是从上向下设计包还是从下向上设计包
应该是从下向上设计包,类的设计应该先于包的设计,包的设计应该是随我们对于逻辑的认识的增长而不断变化的。一开始确定包的结构是错误的,也是主观的。
总结:
包的设计有很多地方和类的设计很相似,但是有宏观了很多,这是由于包在类的上层的缘故。很久以前,我在设计思考的时候没有过多的考虑过包的设计,我通过学习明白并懂得了包设计的重要性,因为从概念上二者相互的影响很小,可实际上,包的改变会伴随类间关系的一些变化,里面就需要使用大量的模式和原则进行,包的设计不但需要很高的技术基础还需要很好的经验。对于我这个小鸟来说,只有根据原则在日常生活中,不断的积累经验了。。。
(共勉之。。。。)
类设计原则:
(1) SRP,单一职责原则(The Single Responsibility Priciple):
就一个类而言,应该仅有一个引起它变化的原因。
(2) OCP,开放封闭原则(The Open-Close Priciple):
软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改;封闭是建立在抽象的基础上的,我们应该只对程序中频繁变化的部分进行抽象,拒绝不成熟的抽象与抽象本身一样重要!OCP背后的主要机制是抽象、多态和继承。
(3) LSP,Liskov替换原则(The Liskov Substitution Priciple):
子类型必须能够替换掉它们的基类型。对于LSP的违反常常会导致使用运行时类型识别(RTTI)(使用一个显式的if语句或者if/else链来确定一个对象的类型)。
(4) DIP,依赖倒置原则(The Dependency Inversion Priciple):
高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节;细节应该依赖于抽象。事实上,使用new来创建对象的任何时候,我们都违反了DIP;但有时,这种违反是无害的,例如依赖于稳定不变的具体类。
(5) ISP,接口隔离原则(The Interface Segregation Interface):
不应该强迫客户依赖于他们不用的方法。接口属于客户,不属于它们所在的类层次结构。
胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定与客户程序的接口,可以实现这个目标,每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户的接口,并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,使得客户程序之间互不依赖。分离客户就是分离接口!
包设计-内聚原则:
(1) REP,重用发布等价原则:
重用的粒度就是发布的粒度。
(2) CCP,共同封闭原则:
包中的所有类对于同一类性质的变化应该是共同封闭的。 一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
(3) CRP,共同重用原则:
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,就应该重用包中的所有类。
包设计-耦合原则:
(1) ADP,无环依赖原则:
在包的依赖关系图中不允许存在环。
(2) SDP,稳定依赖原则:
朝着稳定的方向进行依赖。
(3) SAP,稳定抽象原则:
包的抽象程度应该和其稳定程度一致。
面向对象设计的原则
2008-08-20 16:55
1.类的设计原则
在网上,一般认为灯的设计原则有五类,如下:
1)SRP,单一职责原则,一个类应该有且只有一个改变的理由。
所谓单一职责原则,就是就一个类而言,应该仅有一个引起它的变化的原因。换句话说,一个类的功能要单一,只做与它相关的事情。
这个原则是最简单、最容易理解,却是最不容易做到的事情。这个原则的道理谁都理解,可是在实践中呢?
我们来看一个例子:
if(action.equals("load")&&tab.equals("1")){
request.setAttribute("tabId",tab);
form.set("tabId",tab);
speciManager.loadIncrement(actionForm, request, tab);
}
if(action.equals("Save")&&tab.equals("1")){
System.out.println("inter increment save action");
……
request.setAttribute("tabId",tab);
}
if(action.equals("load")&&tab.equals("2")){
request.setAttribute("tabId",tab);
form.set("tabId",tab);
speciManager.loadMeasureMent(actionForm, request, tab);
}
if(action.equals("Save")&&tab.equals("2")){
……
System.out.println("inter increment save action");
speciManager.loadIncrement(actionForm, request, tab);
form.set("tabId",tab);
request.setAttribute("tabId",tab);
}
一看就知道这个类做了太多的工作,它既要load一个tab为1的页面和一个tab为2的页面;又要save一个tab为1页面和一个tab为2的页面。这个类的代码我只截取了里面很少的一部分,绝大部分的代码我都省略掉了。这段代码写到最后是越来越混乱,直到最后失败。
对照着这个例子,我们再来分析一下为什么要遵守单一职责愿则:
第一、有助于我们分析和编码的思路的清晰。当你的代码里有了三层或以上的if语句或for语句的嵌套的时候,你不要跟我说,你已经把问题分析得很清楚了。多层嵌套的if或for语句只能说明你还没有把问题分析清楚。
第二、使我们的编码、测试和维护变得简单。
第三、将一个个复杂的问题简单化以后,易于代码的重用。当你的代码的多个功能搅和在一起的时候,你是没办法考虑代码的重用的,因为你的每一处代码都有不同。
第四、易于系统的扩展。
2)OCP,开放封闭原则,你应该能够不用修改原有类就能扩展一个类的行为。
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。从另外一个角度讲,就是所谓的“对可变性封装原则”。“对可变性封装原则”意味着两点: 1 .一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。 2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具
3) LSP,Liskov替换原则,派生类要与其基类自相容。
里氏代换原则:就是子类代替父类,程序或者代码的行为不变。例如如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序 P在所有对象o1都换成o2时,程序P的行为没有变化,那么类型T2是T1的子类型。即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种: 1 创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。 2 从B到A的继承关系改写为委派关系。
4) DIP,依赖倒置原则,依赖于抽象而不是实现。
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
5) ISP,接口隔离原则,客户只要关注它们所需的接口。
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
2.包原则
REP,重用发布等价原则,重用的粒度就是发布的粒度。
CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。
CRP,共同重用原则,一个包中的所有类应该是共同重用的。
3 .包之间的耦合性原则
ADP,无环依赖原则,在包的依赖关系图中不允许存在环。
SDP,稳定依赖原则,朝着稳定的方向进行依赖。
SAP,稳定抽象原则,包的抽象程度应该和其稳定程度一致。
1 晨后综合症和包的发布
你曾有过这样的经历吗?工作了一整天,终于完成了某项功能后回家,不料第二天早晨一来却发现那项功能不再工作了。原因是什么呢?因为有人比你走得更晚,并且更改了你所依赖的某些东西!这就是所谓的“晨后综合症"。受“晨后综合症”困扰的团队常常几周时间都无法构建出一个稳定的版本,每个人都忙于一遍遍更改他们的代码,试图使之能够相容与其他人所做的最近更改,从而造成团队士气低落,效率不高,陷入了可怕的集成地狱中。为此有的团队在集成期间禁止 check in ,这显然不是一个好办法,一方面,如果不用技术手段很难避免有意或无意的 check in ,另一方面开发者为了进行后续开发而又不影响当前集成的版本,可能不得不手工进行版本管理, 再有,它使团队成员间的协作也变得困难。
对包进行发布能有效避免晨后综合症的发生。造成晨后综合症的原因在于依赖者所依赖的包是变化的,这就使依赖者工作于一个不稳定的基础之上,对被依赖者的改变,依赖者必须作出相容的改变。采用包的发布机制,依赖者必须选择被依赖包发布的一个特定版本,而发布后的包,其内容是不允许变化的,因此依赖者所依赖的东西就不会改变;同时,被依赖包的任何改变都必须作为一个新版本发布,而依赖者有权决定是否采用这个新版本,换句话说,是否接受被依赖者的改变是由依赖者决定的,而在此之前,依赖者是不得不被动地接受被依赖者的改变。
2 包的设计原则
要对包进行发布,首先要先设计好包,对规模较大的应用来说,划分包的组合很多,仅仅把看起来像是适合在一起的类放进相同的包中,得到的往往是一种不好的包结构:发布很困难、不容易重用、难于更改等等,这种包结构带来的可能是更多的麻烦。显然我们需要一些原则来指导包的划分,以下列出这些原则,前三个原则用来指导把类划分到包中,关注包的内聚性,后三个原则用来处理包之间的相互关系,关注包的耦合性。
2.1 REP 重用发布等价原则
重用的粒度就是发布的粒度。
如果一个包中的软件是用来重用的,那么它就不能再包含不是为了重用目的而设计的软件。换句话说,一个包中的软件要么都是可重用的,要么都不是可重用的。简单地声明一个类是可重用的做法是不现实的,我们所重用的任何东西都必须同时被发布和跟踪。如果一个包同时包含了可重用的类和不可重用的类,那么当不可重用的类发生变化时,就要进行一次包的发布,而原本不受影响的重用者就需要决定是否采用新版本,以及采用新版本后可能的编译,连接和测试工作。这些内耗操作是应该避免的。
2.2 CRP 共同重用原则
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
在大多数情况下,一个可重用抽象需要多个类来表达,该原则规定这些类应该在一个包中,而属于不同抽象的类不应该在一个包中。乍看起来,这条原则和 REP 有点相似,但实际上还是不同的,比如,抽象 A 包括类 A1、A2、A3 ,抽象 B 包含类 B1、B2、B3 ,从 REP 原则来看,这个包没有什么不对,因为该包的软件都是可重用的,但它却违反了 CRP 原则,因为重用者完全可以只重用 A 抽象的类或只重用 B 抽象的类,这样当任一抽象的改变都会导致该包重新发布,虽然该发布对某一抽象的使用者是无意义的,但是他们仍然需要需要重新验证和重新发布,这会白费相当数量的努力。
2.3 CCP 共同封闭原则
包中的所用类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对其他的包不造成任何影响。
REP 和 CRP 关注的都是重用性, CCP 关注的是可维护性。对大多数的应用来说,可维护性的重要性是超过可重用性的。一个变化(包括需求上的,设计上的等等)可能会引起多个类的更改, CCP 要求我们把这些类放在一个包中,同时把那些不受影响的类放到其他的包中,因此一个包只有一个引起变化的原因,一个变化只对一个包产生影响,这样大大减少了重新发布的次数和重新发布对其他包的影响,从而提高了软件的可维护性。
2.4 ADP 无环依赖原则
在包的依赖关系图中不允许存在环。
如果包的依赖关系图中存在环,就会导致环上的所有软件包必须同时发布,如下图,
由于 P3 使用了 P1 中的一个类而形成了依赖环,若要发布 P1 ,必须先发布 P2 和 P3 ,而发布 P3 又要先发布 P1 和 P2,P1、P2、P3 实际上已经变成了同一个大包,于是工作于这些包上的开发人员不可避免地会遭受晨后综合症。
去掉依赖环的方法有两个:
使用DIP。如图,把Y的接口和实现分离,
增加新包。如图,把 P1 和 P3 都依赖的类移到一个新包中
2.5 SDP 稳定依赖原则
朝着稳定的方向进行依赖。
如果一个包被很多包所依赖,那么它就是稳定的,因为要使所有依赖于它的包能够相容于对它所做的更改,往往需要非常大的工作量。一个系统中的所有包并非都应该都是稳定的,因为如果那样的话,系统将很难更改。 SDP 指导我们处理稳定包和不稳定包之间的关系:不稳定的包应该依赖于稳定的包,一个包应该依赖于比他更稳定的包。你设计了一个不稳定的包,期望它能随变化容易地更改,可当它被一个稳定的包依赖后,它就再也不会易于更改了,这就使软件难于修改和变化。
2.6 SAP 稳定抽象原则
包的稳定程度应该和其稳定程度一致。
一个系统的高层构架和设计决策应该被放进稳定的包中,因为这些构架决策不应该经常改变,然而稳定包的不易更改的特点会使这些架构决策不灵活,显然只用稳定性来度量一个包是不够的, SAP 告诉我们:稳定的包也应该是抽象的。它应该包含抽象类,系统通过在其他包中实现该稳定包中的抽象类来进行扩展,而该稳定包无需修改,从而在保持稳定的同时也不失灵活性。
3 包的设计过程
几年前当我还不知道这些包的设计原则时,认为包就是系统的高层的功能分解,应该在项目开始时就设计好,结果遭受了失败。实际上,包的依赖关系图和描绘系统的功能之间几乎没有关系,它是系统可构建性的映射图。项目开始时,我们还不知道系统中有哪些类,更不必说它们之间的关系,在这种情况下不仅很难创建出包依赖关系图,即使创建出来也很可能是不合理的。包的依赖关系结构应该是和系统的逻辑设计一起增长和演化的,项目开始时不必考虑包,系统仍以类的粒度组织,随着开发的进行,类越来越多,对依赖关系进行管理,避免项目开发中出现晨后综合症的需要也不断增长,此时我们应用 CCP 原则对把可能一同变化的类组织成包进行发布,随着系统的不断增长,我们开始关注创建可重用的元素,于是开始使用 CRP 和 REP 来指导包的组合。最后使用 ADP、SDP、SAP 对包图进行度量,去掉不好的依赖。
4 你真的采用了包的发布机制了吗
如果说某个做产品的团队没有采用包的发布机制,它也许会大叫冤枉:我们明明是进行了发布的呀,你看,这不是我们发布的1.0、1.2版本吗?事实上,任何一个产品都离不开包的发布,只不过对这样的团队而言,他们系统里真正意义上的包只有一个,那就是整个系统,而该包的发布常常是在经过了晨后综合症的洗礼之后,是在开发基本完毕后进行的,实际上,我们更应该在产品的开发过程中使用这一机制,这样,产品的开发过程才会以合理,有序的方式进行,产品才能尽快地交付。
“开—闭”原则
面向对象设计的基石是“开—闭”原则。
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。
这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
从另外一个角度讲,就是所谓的“对可变性封装原则”。
“对可变性封装原则”意味着两点:
1 .一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。
2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。
做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具。
里氏代换原则
里氏代换原则:
即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。
如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种:
1 创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。
2 从B到A的继承关系改写为委派关系。
咋一看觉得这个怎么还是面向对象设计的原则呢?这个明明就是Java的语法规则。对,Java是提供了对里氏代换原则在语法上的支持。但是仅仅是语法上,在和现实世界的相符合程度上根本没有提供。所有常常会有不符合里氏代换原则的情况出现。
依赖倒转原则
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。
此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
接口隔离原则
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
合成、聚合复用原则
合成、聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,尽量不要使用继承。
合成、聚合有如下好处:
新对象存取成分对象的唯一方法是通过成分对象的接口。
这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的。
这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。
合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环境中去。
导致错误的使用合成、聚合与继承的一个常见原因是错误的把“Has-a”关系当作“Is-a”关系。如果两个类是“Has-a”关系那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承。
迪米特法则
迪米特法则说的是一个对象应该对其它对象有尽可能少的了解。即只与你直接的朋友通信,不要跟陌生人说话。如果需要和陌生人通话,而你的朋友与陌生人是朋友,那么可以将你对陌生人的调用由你的朋友转发,使得某人只知道朋友,不知道陌生人。换言之,某人会认为他所调用的是朋友的方法。
以下条件称为朋友的条件:
当前对象本身。以参量的形式传入到当前对象方法中的对象。当前对象的实例变量直接引用的对象。当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友。
当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。在类的设计上,只要有可能,一个类应当设计成不变类。在对其它对象的引用上,一个类对其它对象的引用应该降到最低。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/shaidong/archive/2009/04/16/4085378.aspx
1) 重用发布等价原则(The Release Reuse Equivalency Principle (REP))
* 重用的粒度就是发布的粒度
* 一个可重用的包必须为发布跟踪系统所管理,使我们在新版本发布后我们还可以继续使用老版本
* 一个包中的所有类对于同一类用户来讲都应该是可重用的。
2) 共同重用原则(The Common Reuse Principle (CRP))
* 一个包中的所有类应该是共同重用的,如果重用了包中的一个类,就应该重用包中的所有类。
* 一般来说,可重用的类需要与作为该可重用抽象一部份的其它类协作,CRP规定了这些类应该属于同一个包。
* 放入同一包中的所有类应该是不可分开的,其它包仅仅依赖于其中一部份情况是不可能的(不允许的),否则,我们将要进行不必要的重新验证与重新发布,并且会白费相当数量的努力。(一个包依赖于另外一个包, 哪怕只是依赖于其中的一个类也不会削弱其依赖关系)
* CRP倾向于把包做的尽可能的小
3) 共同封闭原则(The Common Closure Principle (CCP))
* 包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中所有类产生影响,而对于其他的包不造成任何影响。
* 这是单一职责原则对于包的重新规定。
* CCP鼓励我们把可能由于同样的原因而更改的所有类共同聚集在同一个地方。将变化限制在最小数据的包中。
* CCP倾向于将包做的尽可能的大。
* CCP有益于维护者(包的作者),而REP和CRP有益于重用者(包的使用者)。
稳定性:包的耦合性原则
4) 无环依赖原则(The Acyclic Dependencies Principle (ADP))
* 在包的依赖关系图中不允许存在环。
* 包的依赖关系图应该是一个有向无环图(DAG(Directed Acyclic Grphic))
* 存在环的系统,很难确定包的构建顺序,事实上,并不存在恰当的构建顺序。
* 打破环的第一个方法:依赖倒置原则,使一个包不再依赖于另一个包,而只是依赖于其抽象接口。
* 打破环的第二个方法: 创建一个新包来包含公共依赖部份。
5) 稳定依赖原则(The Stable Dependencies Principle (SDP))
* 朝着的稳定的方向进行依赖
* 你设计了一个易于更改的包, 其它人只要创建一个对它的依赖就可以使它变的难以更改,这就是软件的反常特性。通过遵循SDP可以避免这种情况。
* 不稳定性度量:I = Ce / (Ca + Ce). Ca: Afferent Coupling. Ce: Efferent Coupling
* SDP规定一个包的I度量值应该大于它所依赖的包的I的度量值,也就是说,I的度量值应该顺着依赖的方向减少。
6) 稳定抽象原则(The Stable Abstractions Principle (SAP))
* 包的抽象程度应该和其稳定程度一致。
* 一个稳定的包同时应该是抽象的,这样,其稳定性就不会导致其无法扩展。一个不稳定的包应该是具体的,这样,因为其不稳定性使得其内部的具体代码易于修改。
* 抽象性度量:A = Na / Nc Na: Number of classes. Nc:Number of abstract classes.
* 创建一个以A为纵轴,I为横轴的坐标图,最稳定,最抽象的包位于左上角(0,1)处, 那些最不稳定,最具体的包位于右下角(1,0)处。
包的设计原则
没有学习这个技术的时候,我对于包的设计都是一种主观的行为,我认为类应该天然在一起的就放入一个包中。没有任何的度量和指导原则。当我看到这些原则的时候,发现自己真的是井底之蛙。。。
对于包的设计一般需要解决四个问题。
1,包中类分配的依据是什么?
2,使用什么原则管理包间的关系?
3,包德设计应该先于类的设计还是相反?
4,包创建后应当用于何种目的?
解决以上及其他问题的六条指导原则
包的设计核心就是低耦合高内聚的,和类的设计核心很相像
第一类(包的内聚原则)
1,重用发布等价原则 REP
重用的粒度就是发布的粒度。当一个包的目的是为了重用,那么这个包下的所有类都应该是重用的,潜在涵义就是,一个包中的类的目标是一致的,而且对于重用应该是按照重用用户类区分的,对于一类重用用户分一个包。
2,共同重用原则 CRP
当一个包被重用的时候,旗下类都会被重用。如果重用类包中一个类那么其下所有类都应该被重用。没有紧密关系的类不应该放在同一个包中。
3,共同封闭原则 CCP
包中所有类对于同一种变化应该是共同封闭的,即每一包只对应一类变化,在系统中,一类变化只影响一个包。由同一变化影响的类放在同一个包中。
第二类(包德耦合原则)
4,无环依赖原则 ADP
首先把包依赖关系影射成一个有向图,看看是否有环路存在。
如果存在环路,那么存在环路的包在更改的时候会影响很多系统包,那么包就需要重新的设计。
解除环路的方法:
(1)使用抽象应用DIP(依赖倒置原则)进行包德隔离。
(2)创建一个新包把存在环路的类放在这个新包中。
5,稳定依赖原则 SDP
朝着稳定的方向进行依赖
稳定类的概念:这个包被很多类依赖但是他不依赖任何包。那么这样的包就是一个稳定包。
稳定性的度量:
I(稳定性)=依赖别的包的数量(出度)/依赖别的包的数量(出度)+被依赖的包的数量(入度)
一个稳定的系统其上层类应该是不稳定的,下层实现应该稳定地,但是上层类还应该灵活易于扩展并能够经受住变化,应该怎么办呢?答案就是抽象类(经典!!),这样一来上层类就是稳定的,而且可以经受变化的类。
重构的方法:DIP
6,稳定抽象原则 SAP
包如果是稳定的那么应该是抽象的,包是不稳定的那么这个包中的类就应该是细节实现类。
稳定意味着抽象
稳定性的度量:
A(稳定性)=包中抽象的类的个数/包中类的总数
SAP是对于SDP的一个升华,一个扩展。也是对于抽象使用的指导原则。
关于度量的使用
通常对于包的设计我们开始的时候把我们认为应该在一起的类放在一个包中,当初步设计结束后,我们就需要通过一张图表来解决包间的问题
我们以I(稳定性)为横轴,以A(抽象性)为纵轴,连接(1,0)(0,1)成直线。这条直线就是期望线,在这条线上的类是即稳定又灵活的。对于趋向(0,0)和(1,1)的类是需要我们进行重构的,特别是趋向(0,0)的类一般都是糟糕的设计。
D‘(类到期望线的距离) = /A+I-1/
是从上向下设计包还是从下向上设计包
应该是从下向上设计包,类的设计应该先于包的设计,包的设计应该是随我们对于逻辑的认识的增长而不断变化的。一开始确定包的结构是错误的,也是主观的。
总结:
包的设计有很多地方和类的设计很相似,但是有宏观了很多,这是由于包在类的上层的缘故。很久以前,我在设计思考的时候没有过多的考虑过包的设计,我通过学习明白并懂得了包设计的重要性,因为从概念上二者相互的影响很小,可实际上,包的改变会伴随类间关系的一些变化,里面就需要使用大量的模式和原则进行,包的设计不但需要很高的技术基础还需要很好的经验。对于我这个小鸟来说,只有根据原则在日常生活中,不断的积累经验了。。。
(共勉之。。。。)
类设计原则:
(1) SRP,单一职责原则(The Single Responsibility Priciple):
就一个类而言,应该仅有一个引起它变化的原因。
(2) OCP,开放封闭原则(The Open-Close Priciple):
软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改;封闭是建立在抽象的基础上的,我们应该只对程序中频繁变化的部分进行抽象,拒绝不成熟的抽象与抽象本身一样重要!OCP背后的主要机制是抽象、多态和继承。
(3) LSP,Liskov替换原则(The Liskov Substitution Priciple):
子类型必须能够替换掉它们的基类型。对于LSP的违反常常会导致使用运行时类型识别(RTTI)(使用一个显式的if语句或者if/else链来确定一个对象的类型)。
(4) DIP,依赖倒置原则(The Dependency Inversion Priciple):
高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节;细节应该依赖于抽象。事实上,使用new来创建对象的任何时候,我们都违反了DIP;但有时,这种违反是无害的,例如依赖于稳定不变的具体类。
(5) ISP,接口隔离原则(The Interface Segregation Interface):
不应该强迫客户依赖于他们不用的方法。接口属于客户,不属于它们所在的类层次结构。
胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定与客户程序的接口,可以实现这个目标,每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户的接口,并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,使得客户程序之间互不依赖。分离客户就是分离接口!
包设计-内聚原则:
(1) REP,重用发布等价原则:
重用的粒度就是发布的粒度。
(2) CCP,共同封闭原则:
包中的所有类对于同一类性质的变化应该是共同封闭的。 一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
(3) CRP,共同重用原则:
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,就应该重用包中的所有类。
包设计-耦合原则:
(1) ADP,无环依赖原则:
在包的依赖关系图中不允许存在环。
(2) SDP,稳定依赖原则:
朝着稳定的方向进行依赖。
(3) SAP,稳定抽象原则:
包的抽象程度应该和其稳定程度一致。
面向对象设计的原则
2008-08-20 16:55
1.类的设计原则
在网上,一般认为灯的设计原则有五类,如下:
1)SRP,单一职责原则,一个类应该有且只有一个改变的理由。
所谓单一职责原则,就是就一个类而言,应该仅有一个引起它的变化的原因。换句话说,一个类的功能要单一,只做与它相关的事情。
这个原则是最简单、最容易理解,却是最不容易做到的事情。这个原则的道理谁都理解,可是在实践中呢?
我们来看一个例子:
if(action.equals("load")&&tab.equals("1")){
request.setAttribute("tabId",tab);
form.set("tabId",tab);
speciManager.loadIncrement(actionForm, request, tab);
}
if(action.equals("Save")&&tab.equals("1")){
System.out.println("inter increment save action");
……
request.setAttribute("tabId",tab);
}
if(action.equals("load")&&tab.equals("2")){
request.setAttribute("tabId",tab);
form.set("tabId",tab);
speciManager.loadMeasureMent(actionForm, request, tab);
}
if(action.equals("Save")&&tab.equals("2")){
……
System.out.println("inter increment save action");
speciManager.loadIncrement(actionForm, request, tab);
form.set("tabId",tab);
request.setAttribute("tabId",tab);
}
一看就知道这个类做了太多的工作,它既要load一个tab为1的页面和一个tab为2的页面;又要save一个tab为1页面和一个tab为2的页面。这个类的代码我只截取了里面很少的一部分,绝大部分的代码我都省略掉了。这段代码写到最后是越来越混乱,直到最后失败。
对照着这个例子,我们再来分析一下为什么要遵守单一职责愿则:
第一、有助于我们分析和编码的思路的清晰。当你的代码里有了三层或以上的if语句或for语句的嵌套的时候,你不要跟我说,你已经把问题分析得很清楚了。多层嵌套的if或for语句只能说明你还没有把问题分析清楚。
第二、使我们的编码、测试和维护变得简单。
第三、将一个个复杂的问题简单化以后,易于代码的重用。当你的代码的多个功能搅和在一起的时候,你是没办法考虑代码的重用的,因为你的每一处代码都有不同。
第四、易于系统的扩展。
2)OCP,开放封闭原则,你应该能够不用修改原有类就能扩展一个类的行为。
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。从另外一个角度讲,就是所谓的“对可变性封装原则”。“对可变性封装原则”意味着两点: 1 .一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。 2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具
3) LSP,Liskov替换原则,派生类要与其基类自相容。
里氏代换原则:就是子类代替父类,程序或者代码的行为不变。例如如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序 P在所有对象o1都换成o2时,程序P的行为没有变化,那么类型T2是T1的子类型。即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种: 1 创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。 2 从B到A的继承关系改写为委派关系。
4) DIP,依赖倒置原则,依赖于抽象而不是实现。
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
5) ISP,接口隔离原则,客户只要关注它们所需的接口。
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
2.包原则
REP,重用发布等价原则,重用的粒度就是发布的粒度。
CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。
CRP,共同重用原则,一个包中的所有类应该是共同重用的。
3 .包之间的耦合性原则
ADP,无环依赖原则,在包的依赖关系图中不允许存在环。
SDP,稳定依赖原则,朝着稳定的方向进行依赖。
SAP,稳定抽象原则,包的抽象程度应该和其稳定程度一致。
1 晨后综合症和包的发布
你曾有过这样的经历吗?工作了一整天,终于完成了某项功能后回家,不料第二天早晨一来却发现那项功能不再工作了。原因是什么呢?因为有人比你走得更晚,并且更改了你所依赖的某些东西!这就是所谓的“晨后综合症"。受“晨后综合症”困扰的团队常常几周时间都无法构建出一个稳定的版本,每个人都忙于一遍遍更改他们的代码,试图使之能够相容与其他人所做的最近更改,从而造成团队士气低落,效率不高,陷入了可怕的集成地狱中。为此有的团队在集成期间禁止 check in ,这显然不是一个好办法,一方面,如果不用技术手段很难避免有意或无意的 check in ,另一方面开发者为了进行后续开发而又不影响当前集成的版本,可能不得不手工进行版本管理, 再有,它使团队成员间的协作也变得困难。
对包进行发布能有效避免晨后综合症的发生。造成晨后综合症的原因在于依赖者所依赖的包是变化的,这就使依赖者工作于一个不稳定的基础之上,对被依赖者的改变,依赖者必须作出相容的改变。采用包的发布机制,依赖者必须选择被依赖包发布的一个特定版本,而发布后的包,其内容是不允许变化的,因此依赖者所依赖的东西就不会改变;同时,被依赖包的任何改变都必须作为一个新版本发布,而依赖者有权决定是否采用这个新版本,换句话说,是否接受被依赖者的改变是由依赖者决定的,而在此之前,依赖者是不得不被动地接受被依赖者的改变。
2 包的设计原则
要对包进行发布,首先要先设计好包,对规模较大的应用来说,划分包的组合很多,仅仅把看起来像是适合在一起的类放进相同的包中,得到的往往是一种不好的包结构:发布很困难、不容易重用、难于更改等等,这种包结构带来的可能是更多的麻烦。显然我们需要一些原则来指导包的划分,以下列出这些原则,前三个原则用来指导把类划分到包中,关注包的内聚性,后三个原则用来处理包之间的相互关系,关注包的耦合性。
2.1 REP 重用发布等价原则
重用的粒度就是发布的粒度。
如果一个包中的软件是用来重用的,那么它就不能再包含不是为了重用目的而设计的软件。换句话说,一个包中的软件要么都是可重用的,要么都不是可重用的。简单地声明一个类是可重用的做法是不现实的,我们所重用的任何东西都必须同时被发布和跟踪。如果一个包同时包含了可重用的类和不可重用的类,那么当不可重用的类发生变化时,就要进行一次包的发布,而原本不受影响的重用者就需要决定是否采用新版本,以及采用新版本后可能的编译,连接和测试工作。这些内耗操作是应该避免的。
2.2 CRP 共同重用原则
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
在大多数情况下,一个可重用抽象需要多个类来表达,该原则规定这些类应该在一个包中,而属于不同抽象的类不应该在一个包中。乍看起来,这条原则和 REP 有点相似,但实际上还是不同的,比如,抽象 A 包括类 A1、A2、A3 ,抽象 B 包含类 B1、B2、B3 ,从 REP 原则来看,这个包没有什么不对,因为该包的软件都是可重用的,但它却违反了 CRP 原则,因为重用者完全可以只重用 A 抽象的类或只重用 B 抽象的类,这样当任一抽象的改变都会导致该包重新发布,虽然该发布对某一抽象的使用者是无意义的,但是他们仍然需要需要重新验证和重新发布,这会白费相当数量的努力。
2.3 CCP 共同封闭原则
包中的所用类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对其他的包不造成任何影响。
REP 和 CRP 关注的都是重用性, CCP 关注的是可维护性。对大多数的应用来说,可维护性的重要性是超过可重用性的。一个变化(包括需求上的,设计上的等等)可能会引起多个类的更改, CCP 要求我们把这些类放在一个包中,同时把那些不受影响的类放到其他的包中,因此一个包只有一个引起变化的原因,一个变化只对一个包产生影响,这样大大减少了重新发布的次数和重新发布对其他包的影响,从而提高了软件的可维护性。
2.4 ADP 无环依赖原则
在包的依赖关系图中不允许存在环。
如果包的依赖关系图中存在环,就会导致环上的所有软件包必须同时发布,如下图,
由于 P3 使用了 P1 中的一个类而形成了依赖环,若要发布 P1 ,必须先发布 P2 和 P3 ,而发布 P3 又要先发布 P1 和 P2,P1、P2、P3 实际上已经变成了同一个大包,于是工作于这些包上的开发人员不可避免地会遭受晨后综合症。
去掉依赖环的方法有两个:
使用DIP。如图,把Y的接口和实现分离,
增加新包。如图,把 P1 和 P3 都依赖的类移到一个新包中
2.5 SDP 稳定依赖原则
朝着稳定的方向进行依赖。
如果一个包被很多包所依赖,那么它就是稳定的,因为要使所有依赖于它的包能够相容于对它所做的更改,往往需要非常大的工作量。一个系统中的所有包并非都应该都是稳定的,因为如果那样的话,系统将很难更改。 SDP 指导我们处理稳定包和不稳定包之间的关系:不稳定的包应该依赖于稳定的包,一个包应该依赖于比他更稳定的包。你设计了一个不稳定的包,期望它能随变化容易地更改,可当它被一个稳定的包依赖后,它就再也不会易于更改了,这就使软件难于修改和变化。
2.6 SAP 稳定抽象原则
包的稳定程度应该和其稳定程度一致。
一个系统的高层构架和设计决策应该被放进稳定的包中,因为这些构架决策不应该经常改变,然而稳定包的不易更改的特点会使这些架构决策不灵活,显然只用稳定性来度量一个包是不够的, SAP 告诉我们:稳定的包也应该是抽象的。它应该包含抽象类,系统通过在其他包中实现该稳定包中的抽象类来进行扩展,而该稳定包无需修改,从而在保持稳定的同时也不失灵活性。
3 包的设计过程
几年前当我还不知道这些包的设计原则时,认为包就是系统的高层的功能分解,应该在项目开始时就设计好,结果遭受了失败。实际上,包的依赖关系图和描绘系统的功能之间几乎没有关系,它是系统可构建性的映射图。项目开始时,我们还不知道系统中有哪些类,更不必说它们之间的关系,在这种情况下不仅很难创建出包依赖关系图,即使创建出来也很可能是不合理的。包的依赖关系结构应该是和系统的逻辑设计一起增长和演化的,项目开始时不必考虑包,系统仍以类的粒度组织,随着开发的进行,类越来越多,对依赖关系进行管理,避免项目开发中出现晨后综合症的需要也不断增长,此时我们应用 CCP 原则对把可能一同变化的类组织成包进行发布,随着系统的不断增长,我们开始关注创建可重用的元素,于是开始使用 CRP 和 REP 来指导包的组合。最后使用 ADP、SDP、SAP 对包图进行度量,去掉不好的依赖。
4 你真的采用了包的发布机制了吗
如果说某个做产品的团队没有采用包的发布机制,它也许会大叫冤枉:我们明明是进行了发布的呀,你看,这不是我们发布的1.0、1.2版本吗?事实上,任何一个产品都离不开包的发布,只不过对这样的团队而言,他们系统里真正意义上的包只有一个,那就是整个系统,而该包的发布常常是在经过了晨后综合症的洗礼之后,是在开发基本完毕后进行的,实际上,我们更应该在产品的开发过程中使用这一机制,这样,产品的开发过程才会以合理,有序的方式进行,产品才能尽快地交付。
“开—闭”原则
面向对象设计的基石是“开—闭”原则。
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。
这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
从另外一个角度讲,就是所谓的“对可变性封装原则”。
“对可变性封装原则”意味着两点:
1 .一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。
2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。
做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具。
里氏代换原则
里氏代换原则:
即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。
如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种:
1 创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。
2 从B到A的继承关系改写为委派关系。
咋一看觉得这个怎么还是面向对象设计的原则呢?这个明明就是Java的语法规则。对,Java是提供了对里氏代换原则在语法上的支持。但是仅仅是语法上,在和现实世界的相符合程度上根本没有提供。所有常常会有不符合里氏代换原则的情况出现。
依赖倒转原则
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。
此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
接口隔离原则
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
合成、聚合复用原则
合成、聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,尽量不要使用继承。
合成、聚合有如下好处:
新对象存取成分对象的唯一方法是通过成分对象的接口。
这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的。
这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。
合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环境中去。
导致错误的使用合成、聚合与继承的一个常见原因是错误的把“Has-a”关系当作“Is-a”关系。如果两个类是“Has-a”关系那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承。
迪米特法则
迪米特法则说的是一个对象应该对其它对象有尽可能少的了解。即只与你直接的朋友通信,不要跟陌生人说话。如果需要和陌生人通话,而你的朋友与陌生人是朋友,那么可以将你对陌生人的调用由你的朋友转发,使得某人只知道朋友,不知道陌生人。换言之,某人会认为他所调用的是朋友的方法。
以下条件称为朋友的条件:
当前对象本身。以参量的形式传入到当前对象方法中的对象。当前对象的实例变量直接引用的对象。当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友。
当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。在类的设计上,只要有可能,一个类应当设计成不变类。在对其它对象的引用上,一个类对其它对象的引用应该降到最低。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/shaidong/archive/2009/04/16/4085378.aspx
发表评论
-
Java对Json的工具类 JsonUtil 绿色版
2011-04-19 10:48 2477绿色版,无污染,无需下载第三方包.转自某某博客. packa ... -
模型是是搜索
2011-03-17 00:37 1150#!/usr/bin/env python # -*- co ... -
Java 调用浏览器访问指定URL. 多平台
2011-02-16 13:46 2910/////////////////////////////// ... -
Java Web 项目读取配置文件
2010-10-28 18:18 2856在开发过程中,我们经常会遇到读取配置文件的情况,对于配置文件的 ... -
j=j++与j=++j的区别
2010-09-21 16:28 1768jvm里面有两个存储区,一个是暂存区(是一个堆栈),另一个是变 ... -
JSON与JAVA数据的转换
2010-09-15 14:29 1016JSON与JAVA数据的转换 关键字: json java ... -
对web.xml 的几点配置心得。包括mime-mapping
2010-08-31 23:09 19631.指定了自己的javaEncoding (参考 http:/ ... -
JasperReports学习笔记
2010-08-30 13:03 1372reference: http://www.javaworl ... -
JFreeChart 教程二
2010-08-25 16:22 1162JFreeChart是一组功能强大、灵活易用的Java绘图AP ... -
JFreeChart 教程一
2010-08-25 16:20 1016以下是需要画一个折线图查到的资料(放到这里备用): 原文地址 ... -
我和iText的第一次亲密接触
2010-08-19 22:58 1397要生成pdf文件,在网上查了下资料,首选iText,跟着大家走 ... -
引入多个Jar~
2010-08-19 13:49 595Zhe画 13:55:50 java命令引入jar时可以- ... -
Jetty7 Continuation 学习(一)
2010-08-19 08:44 1907Jetty7发布了,Jetty7支持servlet 2.5,且 ... -
jetty快速入门
2010-08-17 15:13 2832(0) Jetty 是一个开源的ser ... -
JNI(java 调用 本地接口)Tomcat的JNI库加载问题解决办法
2010-08-15 10:17 6728Tomcat的Release Notes 里有这么一段话: = ... -
Java入门--认识理解Java中native方法
2010-08-15 09:38 955Java不是完美的,Jav ... -
用digester简化xml文档处理
2010-08-15 00:21 1038用digester简化xml文档处理java 2010-05- ... -
关于URL特殊符号的问题~
2010-08-14 15:20 1072HTML 格式编码的实用工具类。该类包含了将 String 转 ... -
InheritableThreadLocal 线程变量
2010-07-29 11:40 2296ThreadLocal有个缺陷,在子线程里无法访问父线程的变量 ... -
java基础补疑
2010-07-28 11:53 818java语言的关键字,变量修饰符,如果用transient声明 ...
相关推荐
45. **同质性原则**:类的成员应具有相似的属性和行为,保持类的内聚性。 46. **静态属性的使用**:静态属性应谨慎使用,避免其带来的全局状态问题。 47. **动态属性**:动态属性应清晰标记,以区别于实例属性。 ...
这意味着每个类应有且仅有一个主要职责,这样可以提高代码的内聚性,降低耦合度。例如,一个处理用户界面逻辑的类不应该同时负责数据存储,这两个职责应该分开处理。 2. **开闭原则(Open-Closed Principle, OCP)*...
如果一个变化因子影响到包内多个类,则这些类都应进行相应的改变;如果影响的只是单个类,则这种变化不应当影响到其它类。此外,发布的模块粒度应与复用粒度保持一致,从而使得每个模块都是可复用和可管理的。 在...
1. 单一职责原则:一个类应该只负责一项职责,以提高类的内聚性和降低耦合度。 2. 接口隔离原则:客户端不应该被迫依赖于它不需要的接口,应将接口拆分成更小、更具体的接口。 3. 依赖倒转原则:依赖于抽象,而不...
- **应用层**:关注内聚与耦合、实施与曝光、变体与版本等问题。 - **信息层**:区分内部与外部、内容与上下文、抽象与具体的区别。 - **技术层**:考虑捆绑依赖、后期绑定和选择合适的工具等方面。 #### 四、...
总结来说,GRASP原则是软件设计中的重要指导思想,特别是高内聚和低耦合原则,它们有助于构建出易于维护、可扩展的软件系统。通过理解并应用这些原则,开发者可以更好地组织代码,提高团队协作效率,以及应对未来...
高内聚则是指模块内部的组成部分高度相关,降低了改动引发的复杂性,从而降低了维护成本。 此外,保持局部变化的原则也是关键。当设计允许局部改变而不影响其他部分时,修改成本和风险都会降低。同时,简洁的设计...
- **内聚性**:衡量模块内部各部分紧密度的度量。 - 功能内聚:模块执行单一的功能。 - 顺序内聚:模块的各部分按照一定的顺序执行任务。 #### 4. 模块化设计 - **模块**:软件中的一个可重用的单元,具有特定的...
- 高内聚:Dubbo遵循了重用发布等价原则(REP),包的发布粒度与可重用粒度相匹配,保证了模块的独立性。共同重用原则(CRP)确保包内的类紧密协作,共同封闭原则(CCP)强调包内类对变更的统一响应,避免影响其他...
1. 类的组织:每个类应专注于一个单一的责任,遵循高内聚低耦合的原则。 2. 函数的粒度:函数应尽可能短小,每个函数只做一件事情。 3. 避免过多的全局变量:尽量使用局部变量,减少全局变量的使用以降低代码...
**应用场景**:当需要确保类具有高内聚性时。 #### 2. 里氏替换原则(Liskov Substitution Principle) 子类必须能够替换它们的基类。 **应用场景**:当需要确保继承结构合理时。 #### 3. 依赖倒置原则...
2. **高内聚**:接口内的方法应该具有高度的相关性,这意味着它们通常服务于同一目的或功能。这样可以提高代码的可读性和可维护性。 3. **适当地平衡接口粒度**:虽然接口应该尽可能小,但也不能过于细碎,以免增加...
1. **高内聚,低耦合**:每个模块应尽可能包含相关性强的功能,减少模块间的依赖,使模块独立性更强,便于维护和升级。 2. **模块功能单一**:每个模块应专注于一个特定的任务,避免一个模块承担过多职责,导致复杂...
在OSGi中,模块通过添加元数据来定义哪些类应该被暴露,哪些类应该被隐藏,从而控制了类的可见性和可用性,使得系统达到高内聚和低耦合。 接着,文章提出了一个关键问题:为什么需要模块化?作者详细阐述了OSGi中...
- **目的**:提高代码的内聚性,降低类之间的耦合。 - **里氏替换原则(Liskov Substitution Principle)** - **定义**:子类型必须能够替换它们的基类型。 - **目的**:确保子类可以替换父类并且不会影响程序的...
3. **高内聚性原则**:每个微服务都包含与其功能相关的所有行为和数据,使得各个服务模块能够自成体系,对外界的依赖性降到最低。这种设计减少了不同服务之间的数据流动和交互,从而提高了系统的稳定性和效率。 ###...
- **内聚性**:服务高度专注,代码易于理解和维护。 - **开发效率**:小团队可以快速开发、部署和迭代。 - **技术多样性**:每个服务可选用最适合的技术栈。 - **自动化部署**:通过持续集成工具简化部署流程。 ...
8. 高内聚:模块内部元素之间关系紧密,减少不必要的相互依赖。 9. 精简性:只包含必要的功能,避免过度设计。 10. 层次性:采用分层结构,如三/四层架构,有利于分工和后期重构。 软件架构的定义在不同人眼中可能...
- 服务应具有内聚性和完整性。 - 实现细节应封装在服务内部。 - 服务需适应多种调用模式。 11. **接口技术规范**: - 接口以WSDL描述的WebService形式发布。 - 使用HTTP通讯协议,支持SOAP访问协议。 - 数据...
- 在软件设计时,遵循“低耦合高内聚”的原则是非常重要的,以提高模块的独立性和可维护性。 8. **软件测试参考文档**: - 进行软件测试时,通常需要参考需求规格说明书、设计文档等。 9. **维护方式**: - ...