空间站

北极心空

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

一个良好的面向对象设计需要遵循一些基本原则,如单一职责原则(SRP)、开放-封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口分离原则(ISP)等。

1、 单一职责原则(SRP)
描述:就一个类而言,应该仅有一个引起它变化的原因。
应用:在构造对象时,将对象的不同职责分离至两个或多个类中,确保引起该类变化的原因只有一个。
带来的好处:提高内聚、降低耦合。
个人观点:该原则可以有效降低耦合,减少对不必要资源的引用。但后果是造成源文件增多,给管理带来不便,所以在实际应用中,可以对经常使用或经常需要改动的模块应用该原则。

2、 开放-封闭原则(OCP)
描述:"对于扩展是开放的"(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,可以对模块进行扩展,使其具有满足改变的新行为。也就是说,我们可以改变模块的功能。"对于更改是封闭的"(Close for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。
应用:高级语言中的接口与虚拟类。
带来的好处:提高灵活性、可重用性、可维护性。
个人观点:OCP的关键是抽象,抽象的目的是创建一个固定却能够描述一组任意个可能行为的基类。而这一组可能的行为则表现为派生类。对于基类的更改是封闭的,所以它里边的方法一旦确定就不能更改(对接口里的方法进行更改将带来灾难性的后果)。模块通过抽象基类进行引用,对派生类的扩展并不影响整个模块,所以它是开放的。遵循OCP的代价也是昂贵的,创建正确的抽象是要花费开发时间和精力的,同时抽象也增加了软件设计的复杂性。因此有效的预知变化是OCP设计的要点,这需要我们进行适当的调查,提出正确的问题,并利用我们的经验和一般常识来做出判断。正确的做法是,只对程序中频繁变化的部分做出抽象,拒绝不成熟的抽象和抽象本身一样重要。

3、 Liskov替换原则(LSP)
描述:若对每个类型S的对象O1,都存在一个类型T的对象O2,使得在所有针对T编写的程序P中,用O1替换O2后,程序P行为功能不变,则S是T的子类型。
应用:在实现继承时,子类型(subtype)必须能替换掉它们的基类型(base type)。如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。
个人观点: LSP是使OCP成为可能的主要原则之一,对LSP的违反将导致对OCP的违反,同时二者是OOD中抽象和多态的理论基础,在OOPL中表现为继承。在高级语言(JAVA、C#)中,只要我们严格按照接口和虚拟类的语法规范来做就能很好遵循此原则,另外我们还应该避免一些更微妙的违规情况。举个例子,正方形和矩形,矩形可以做为正方形的基类,因为正方形也是一种矩形,但对于正方形来说,setWidth()和setHeight()是冗余的,且容易引起错误,这样的设计就违反了LSP原则。如果有两个具体类A和B之间的关系违反了LSP,可以在以下两种重构方案中选择一种:1 .创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。 2 .从B到A的继承关系改写为委派关系。

4、 依赖倒置原则(DIP)
描述:A .高层模块不应该依赖于低层模块。二者都应该依赖于抽象。B .抽象不应该依赖于细节。细节应该依赖于抽象。
应用:要依赖抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
结论:DIP虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用将导致大量的类文件。给维护带来不必要的麻烦。所以,正确的做法是只对程序中频繁变化的部分进行依赖倒置。

5、 接口隔离原则(ISP)
描述:不要强迫客户依赖于它们不用的方法。
应用:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
结论:使用多个专门的接口比使用单一的接口要好。

遵循以上原则,可以使我们的软件更具灵活性,强壮性。但灵活是需要付出代价的,由多态带来的性能损失就是最明显的一个问题。所以我们需要权衡,需要做出选择,在灵活与性能之间做出选择。

追本溯源,促使我们使用这些原则的原因是为了满足需求的变更,于是需求分析就显得格外重要。然而不管怎么充分的需求分析都可能遭遇需求变更,于是预测变化就成了一个让人头痛的事。还是让我们来看看敏捷设计(XP)是怎么解决这些问题的:"敏捷开发人员不会对一个庞大的预先设计应用那些原则和模式,相反,这些原则和模式被应用在一次次的迭代中,力图使代码以及代码所表达的设计保持干净。"也就是说敏捷设计通过快速的迭代来刺激变化,让这些变化及早暴露,再根据变化进行相应改动。很明显这要比一次性完整设计轻松容易的多。

最后引用透明在书评中的一句话来结束这篇blog。"软件开发的全部艺术就是权衡:在简单与复杂之间权衡,在一种方案与另一种方案之间权衡。如果把每个问题、每个权衡的利弊都考虑得清清楚楚,恐怕开发一个应用程序的成本会高得惊人。所以,很多时候我们更依赖自己的审美眼光,用平静的心去设计一个赏心悦目的系统。"

- 作者: for_job 2004年08月27日, 星期五 16:19

posted on 2006-11-13 10:31 芦苇 阅读(163) 评论(0)  编辑  收藏 所属分类: JAVA

只有注册用户登录后才能发表评论。


网站导航: