第十八讲 设计原理(Design Principles) 类原理 类包原理
设计原理 ——类原理 1、开放封闭原理(Open Closed Principle,OCP) 对象类应该是开放的以便于扩展,又要是封闭的以利于修改。我们应该可以不改动原有类的基础上,就能够在系统中增加新的功能。 OCP中的一个原则就是减少类之间的耦合,在抽象层次建立类之间的关联。不要在两个具体类之间建立关系,而在具体类和抽象类之间建立关系,或者按Java中的说法,在具体类和接口之间建立关系。 OCP是所有关于类的原理中最重要的一个。实际上其它关于类的原理都是从OCP演化而来的。
设计原理 ——类原理 2、Liskov替代原理(Liskov Substitution Principle,LSP) LSP可以看作是OCP的一种扩充,它是Barbara Liskov的贡献,从Bertrand Meyer的契约式设计(Design by Contract)演化而来。 OCP以抽象关联为中心。LSP虽然也很倚重于抽象关联,但也依赖于先验条件(precondition)和后验条件(postcondition),先验条件和后验条件的概念在约定式设计中得到形式化,这就是LSP与约定式设计之间的关系。 先验条件是关于在方法被调用之前所必须满足的条件的约定。后验条件是关于在方法被调用后所必须达到的条件的约定。如果先验条件不满足,就不应该调用该方法;如果后验条件不满足,方法就不应该返回。 在Java中,为了遵从LSP,我们应该确保开发者对基类的每一个方法都定义了其前提条件和事后条件。在定义子类时,我们必须遵从这些前提条件和事后条件。 public abstract deposit(int amt) throws InvalidAmountException public abstract deposit(int amt) throws Exception
设计原理 ——类原理 依赖于抽象类,不要依赖于具体类。 DIP形式化了抽象耦合的概念,清楚地表述了我们应该在抽象层耦合,而不要在具体层耦合。 3、依赖性倒置原理(Dependency Inversion Principle,DIP) 依赖于抽象类,不要依赖于具体类。 DIP形式化了抽象耦合的概念,清楚地表述了我们应该在抽象层耦合,而不要在具体层耦合。 从编程上来说,当我们不确切是否类的实现在将来会改变时,都应该应用这一原理。
设计原理 ——类原理 4、接口分离原理(Interface Segregation Principle,ISP) 多个专门的接口优于一个单一的通用接口。 任何接口都应具有高内聚性。在Java中,接口是一种引用数据类型,其上可以定义方法,但不能有实现。实质上,接口就是所有方法都是抽象方法的抽象类。 在定义接口时,明确接口在应用程序中所扮演的角色很重要。实际上,接口提供了灵活性:它可以使对象呈现为一种接口对象类型。因此,接口就是对象在其生命期中在某些时刻所扮演的某种简单的角色。 我们在设计接口及接口上的操作时,应当遵循不要让接口具有多重角色。一个接口应该保证实现该接口的类的实例对象可以只呈现为单一的角色。
设计原理 ——类原理 5、构成重用原理(Composite Reuse Principle,CRP) 继承可以看作是一种在特殊性中概括出一般性的关系——就是说,类树中级别高的类是所派生类的一种概括性版本。换句话说,基类总是需要定义一组缺省的特性,这些特性必须能够应用于从它派生出的任何类。实际上,任何时候如果出现了必须对基类的方法进行重载的话,都说明,我们的基类不是一个对所有其派生类的绝好的概括,它太具体化了,在这里不应该做为基类。因此,如果我们要在基类中定义缺省的行为的话,一定要保证这个行为适用于所有的派生类。 。 对象构成物的多态性优于继承。
设计原理 ——类原理 6、最少知识原理(Principle of Least Knowledge,PLK) PLK也称为Demeter法则,其基本思想是避免调用那些调用其它对象的方法所返回的对象上的任何方法。 在一个类上的操作中,只有类本身、操作的参数对象、操作中创建的对象和类包含的实例对象等的操作可以被调用。 PLK原理建议我们尽量调用所包含的对象的方法,不要从中获得其它对象的引用。其主要好处在于,我们不需要了解被调用方法的对象的任何内部组成结构。
设计原理 ——类原理 public class Sample{ public class Sample{ public void lawTest(AnObjet o) { AnotherObject ao = o.get(); ao.doSomething(); } public class Sample{ public void lawTest(AnObjet o) { o.doSomething(); }
设计原理 ——类原理 6、最少知识原理(Principle of Least Knowledge,PLK) 实际上,Java中的许多类也是用这种方式解决这个问题的。
设计原理 ——类包原理 在开发的整个过程中,开发团队通常要花费大量时间来设计系统。但是大多数时间是花在创建一个灵活的类结构上,只有少部分时间被用来设计系统的包结构。包之间的关系通常不被考虑,将类如何分配到包也没有经过细致的思考。这种疏忽是不幸的,因为包之间的关系和类之间的关系一样重要。 如果类Client与类Service有关系,那么很明显,包含这两个类的包之间也应存在一定的关系,通常称之为“包的依赖”。 如果对P1包的内容的改变会影响到另一个包P2的内容,那么我们就说P1在P2上有一个包的依赖。
设计原理 ——类包原理 1、版本重用等价原理(Release Reuse Equivalency Principle,REP) 我们知道,任何Java类可以只在一个包中。因此,如果想要使用一个类的服务,我们不仅必须引用这个类,而且也必须明确地引用其所在的包。如果不这么做将导致编译时错误。因此,为了使用一个类,我们必须确保其所在的包也被引用。 因为包被引用了,所以我们能够使用包中任何公共类所提供的服务。因此,当我们当前只需要包含包中的一个类的服务时,所有类的服务对于我们来说都是可用的。 因此,版本单元就是我们的重用单元,这就是版本重用等价原理。
设计原理 ——类包原理 2、通用闭包原理(Common Closure Principle,CCP) 类内聚强调了要创建功能完善而且不跨越职责界限的类。包内聚侧重于包内部的类,强调由整个包提供的所有服务。 在开发过程中,当对一个类的改变可能指示了对另一个类的改变时,最好两个类放在同一个包中。 CCP在概念上可能容易理解;但是应用它则比较困难,因为我们能够以这种方式将类分组在一起的唯一方法在于,我们能够预见将要发生的改变以及这些改变的结果会在哪些依赖类上产生影响 这种预见可能经常是不正确的或者不全面的,但不管怎样,将类放到各个包中应该经过深思熟虑。
设计原理 ——类包原理 3、通用重用原理(Common Reuse Principle,CreP) 如果一个类依赖于一个不同包中的其他一个类,那么它实际上依赖于那个包中的所有类,虽然引用是间接的。 不能被一起重用的类不应该放在一起。类一起被重用与类一起被改变是两个不同的概念。 REP和CreP强调的是重用,而CCP强调的则是维护。
设计原理 ——类包原理 4、无环依赖原理(Acyclic Dependencies Principle,ADP) 应该尽量避免形成一个应用的包的环形依赖。换句话说,包应该形成一个有向无环图(Directed Acyclic Graph,DAG)。 环形依赖导致不同包中的类是耦合的,所以这些不同的包变成紧密耦合。它会对REP产生消极的影响以至于所有这些类都必须在同一个包中,但这样一来,CCP也被严重影响了。 如果我们确实确定有环形依赖,最简单的解决办法是找出导致这种依赖结构的类,把它们放到一个单独的包中。
设计原理 ——类包原理 5、稳定依赖原理(Stable Dependencies Principle,SDP) 稳定性是一种表示系统忍受变化的容易程度的特点,在Java中,我们更关心包的弹性。 那些可能不断发生改变的包是不太稳定的,这就意味着它拥有较少的外部调用依赖性,较多的调用外部依赖性。 那些不太经常发生变化的包是比较稳定的,这就意味着它拥有更多的外部调用依赖性,较少的调用外部依赖性。 由于依赖关系的准则使得包可能是稳定的,也可能不够稳定,因此,很明显地,我们应该自然而然地依靠稳定性准则。任何产生的依赖性都应该是深思熟虑的结果,并且应该对整个应用的稳定性有着重要的影响。理想的,应该只在那些稳定的包中引入依赖性。
设计原理 ——类包原理 6、稳定抽象原理(Stable Abstraction Principle,SAP) 在包的层次结构中,不够稳定的包应出现在包层次中的顶层,而较稳定的包出现在包层次中的底层。基于这一点,一个处于包层次中较底层的包必须是更有弹性的包,这是很重要的,因为对这些包做改变会有很大的影响。 依靠稳定性准则,应该将抽象的类、接口放在更稳定的包中,这有助于确保那些依赖性较强的类拥有更高的稳定性。 包括许多抽象类和接口的更稳定的包应该被大量的依赖。 包括很多具体类的不够稳定的包不应该被大量的依赖。 稳定的包应该是抽象包。
下一讲:设计模式 基本概念 基本要素 模式分类 实例