面向对象设计方法L06B可重用和可维护原则—依赖倒换原则.wxz为何而“倒转”倒转的意义:传统的过程性系统的设计办法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次。倒转原则就是要把这个错误的依赖关系倒转过来,这就是“依赖倒转原则”的来由。(抽象层次依赖于具体层次)为何而“倒转”抽象层次含有宏观的和重要商务逻辑,是必然性的体现,而具体层次是含有一些次要的与实现有关的算法和逻辑,带有相当大的偶然性选择。复用与可维护性的“倒转”从复用的角度来看,高层次的模块是设计者应当复用的。但是传统的过程性的设计中,复用却侧重于具体层次模块的复用,比如算法复用,数据结构复用,函数库复用等,都不可避免是具体层次模块的复用。较高层次的结构依赖于较低层次的结果,接下去不断的循环直到依赖于每一行的代码。较低层次的修改就会影响到较高层次的修改,直到高层次逻辑的修改传统的做法也强调具体层次上的可维护性,而不是高层次上的可维护性。从复用的意义上来说,既然抽象层次含有一个应用系统最重要的宏观商务逻辑,是作战略性判断和决定的地方,那么抽象层次就应当是较为稳定的,应当是复用的重点。由于现有的复用侧重于具体模块和细节的复用,因此,“倒转”一词是指复用应当将复用的重点放在抽象层次上。最重要的宏观商务逻辑也应当是维护重点遵守依赖倒转原则会带来复用和可维护性的“倒转”。依赖倒转原则三种耦合关系1.零耦合:如果两个类没有耦合关系,就称为零耦合。2.具体耦合:具体性耦合发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成的。3.抽象耦合:抽象耦合关系发生在一个具体类和一个抽象类(或者Java接口)之间,使两个必须发生联系的类之间存有最大的灵活性。什么是依赖倒转原则简单的说,DIP要求客户端依赖于抽象耦合。依赖倒转的表述是:抽象不应当依赖于细节,细节应当依赖于抽象。另一种表述是:要针对接口编程,不要针对实现编程。(要实现,一个具体Java类应当只实现Java接口和抽象Java类中声明过的方法,而不应当给出多余的方法)倒转依赖关系强调一个系统内的实体之间关系的灵活性。变量的静态类型和真实类型变量被声明时的类型叫做变量的静态类型,变量所引用的对象的真实类型叫做变量的实际类型。Listemployees=newVector();上述代码中,employees变量的静态类型是List,而它实际类型是Vector。引用对象的抽象类型在很多情况下,一个Java程序需要引用一个对象。如果这个对象有一个抽象类型的话,应当使用这个抽象类型作为变量的静态类型。这就是针对接口编程的含义。如果“蛋”代表抽象,“鸡”代表具体,那么,依赖倒转原则相当于在说,“鸡”应当依赖于“蛋”,先有蛋,后有鸡。如右图所示,那么变量的声明应当是:蛋x=new鸡();蛋抽象类鸡具体类例子:创建employee类尽量不要使用:Vectoremployees=newVector();应当使用:Listemployees=newVector();区别:前者使用具体类作为变量的类型,而后者使用一个抽象类(List是Java接口)作为类型。好处:在决定将Vector类型转换成ArrayList时,需要改动的很少:Listemployees=newArrayList();引用对象的抽象类型的优点程序具有更好的灵活性,因为除去调用构造子的一行语句之外,程序的其余部分根本察觉不到变化。因此,只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括变量的类型声明,方法返还类型的声明,属性变量类型的声明等。对象的创建一般而言,在创建一个对象时,Java语言要求使用new关键词以及这个类型本身。如上所示。一旦对象已经被创建出来,那么就可以灵活的使用这个对象的抽象类型来引用它。因此,Java语言创建一个对象的过程是违背“开—闭”原则以及依赖倒转原则的。虽然在这个类被创建出来以后,可以通过多态性使得客户端依赖于其抽象模型。正是由于这个原因,设计模式给出了多个创建模式,特别是工厂模式,用于解决对象创建过程中的依赖倒转问题。怎样做到依赖倒转原则以抽象方式耦合是依赖倒转原则的关键。由于一个抽象耦合关系总要涉及到具体类从抽象类继承,并且保证在任何引用到基类的地方都可以转换成其子类,因此,里氏代换原则是依赖倒转原则的基础。抽象方式耦合局限:在某些情况下,如果一个具体类发生变化的可能性很小,那么抽象耦合能发挥的好处便十分有限,这时使用具体耦合反而会更好。依赖倒转原则是OO设计的核心原则,设计模式的研究和应用是以依赖倒转原则为指导原则的。下面就举几个设计模式的例子加以说明。工厂方法模式针对问题:应当使消费一个对象的客户端只依赖于对象的抽象类型,而不是它的具体类型。但是,Java语言要求在将一个(具体)类实例化的时候,必须调用这个具体类的构造子,所以Java语言给出的类的实例化方法无法做到只依赖于抽象类型。简单工厂模式将这个违反“开—闭”原则以及依赖倒转原则的做法封装到一个类里。工厂方法模式是类的创建模式,又叫做虚拟构造子模式或多态性工厂模式。它的用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。如下页图所示:高级商业逻辑抽象工厂具体产品具体工厂具体工厂抽象产品具体产品工厂方法模式的优点:在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将创建的工作交给子类去做。这个核心类成为一个抽象工厂的角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类实例化的细节。这使得工厂方法模式可以允许系统在不修改具体工厂角色的情况下引进新的产品,使其具有超越简单工厂模式的优越性。模板方法模式模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。如下图所示:高级商业逻辑抽象模板角色具体模板角色具体模板角色模板方法模式特点支持依赖倒转原则。具体子类不能影响抽象类的宏观逻辑,而抽象类的改变则会导致细节逻辑的改变。迭代子模式迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。如下图所示:Iterator是迭代器的意思,它的作用是一次产生一个数据项,直到没有为止。高级商业逻辑Iterator接口Iterator的实现Iterator的实现这样一来,聚集的内部结构的改变就不会涉及到客户端,从而实现对抽象接口的依赖。Java对抽象类型的支持Java语言提供了Java接口和Java抽象类实现对抽象类型的支持。二者区别1·Java抽象类可以提供某些方法的部分实现,而Java接口不可以2·对于Java抽象类,Java语言限制一个类只能从最多一个超类继承;而Java接口,一个类可以实现任意多个Java接口3·从代码重构的角度来讲——将一个单独的Java具体类重构成一个Java接口的实现是很容易的;而为一个已有的具体类添加一个Java抽象类却不那么容易4·Java接口是定义混合类型(MixinType)的理想工具将一个单独的Java具体类重构成一个Java接口的实现只需要声明一个Java接口,并将重要的方法添加到接口声明中,然后在具体类定义语句后面加上一个合适的implements子句就可以了。为一个已有的具体类添加一个Java抽象类由于这个具体类有可能有一个超类,这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新定义的抽象类必定处于整个类型等级结构的最上端,从而使等级结构中的所有成员都会受到影响。Java接口定义混合类型所谓混合类型,就是在一个类的主类型之外的次要类型。比如Hashtable类就有多个类型。它的主要类型是Map,这是一种Java聚集。而Cloneable接口则给出一个次要类型,这个类型说明这个类的实例是可以安全克隆的。同样,Serializable也是一个次要类型,它表明这个类的实例是可以串行化的。如下所示:DictionaryMapCloneableJava.io.SerializableJava.util.Hashtable联合使用Java抽象类和Java接口由于Java抽象类具有提供缺省实现的优点,而Java接口具有其他所有的优点,如下图所示,所以联合使用两者就算一个很好的选择。《interface》抽象类型缺省实现联合使用的实现首先,声明类型的工作仍然是由Java接口实现的,但是同时给出的还有一个Java抽象类,为这个接口给出一个缺省实现。其他同属于这个抽象类型的具体类可以选择实现这个Java接口,也可以选择继承自这个抽象类。联合使用的实现(续)如果一个具体类直接实现这个Java接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的方法,因为它可以从抽象类中自动得到这些方法的缺省实现。如果需要向Java接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法。这其实就是缺省适配模式。一个例子:账号,账号的种类和账号的状态Account类有两个聚类关系——均是静态抽象类型,一个指向AccountType(该类型有Saving和Checking两种具体子类),另一个指向AccountStatus(该类型有Open和OverDrawn两种具体实现)。如下图所示:Account+Account(accType:AccountType)+depoist(amt:float)voidAccountStatus+sendCorrespondence():voidAccount+depoist(amt:float)voidSaving+depoist(amt:float)voidAccountType+depoist(amt:float)voidOverdrawn+sendCorrespondence():voidOpen+sendCorrespondence():void代码2:Account类的源代码PublicclassAccount{privateAccountTypeaccountType;privateAccountStatusaccountStatus;publicAccount(AccountTypeaccType){//writeyourcodehere}publicvoiddeposit(floatamt){//writeyourcodehere}}代码3:抽象类AccountType源代码abstractpublicclassAccountType{publicabstractvoiddeposit(floatamt);}代码4:抽象类AccountStatus源代码abstractpublicclassAccountStatus{publicabstractvoidsendCorrespondence();}代码5:具体实现类Saving源代码publicclassSavingextendsAccountType{publicvoiddeposit(floatamt){//writeyourcodehere}}代码6:具体实现类Checking源代码publicclassCheckingextendsAccountType{publicvoiddeposit(floatamt){//writeyourcodehere}}代码7:具体实现类Open源代码pub