Java设计模式——依赖倒转原则

来源:互联网 发布:淘宝论坛怎么进入 编辑:程序博客网 时间:2024/06/06 04:04

一、什么是依赖倒转原则?

依赖倒转原则讲的是,要依赖于抽象,不要依赖于具体

实现“开-闭”原则的关键是抽象化,并且从抽象化导出具体化实现。“开-闭”原则是面向对象设计的目标,依赖倒转原则是面向对象设计的主要机制。

依赖倒转原则的另一种表述,要针对接口编程,不要针对实现编程。

针对接口编程是说,应该使用Java接口或Java抽象类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。

不要针对实现编程是说,不应当使用具体Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。

要保证做到这一点,一个具体Java类应当只实现Java接口或Java抽象类中声明过的方法,而不应当给出多余的方法。

二、怎样做到依赖倒转原则

以抽象方式耦合是依赖倒转原则的关键。由于一个抽象耦合关系总要涉及到具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成子类,因此,里氏代换原则是依赖倒转原则的基础

在抽象层次上的耦合虽然具有灵活性,但也带来了额外的复杂性。在某些情况下,如果一个具体类发生变化的可能性非常小,那么抽象耦合能发挥的好处便十分有限,这时使用具体耦合反而会更好。

依赖倒转原则是面向对象设计的核心原则,设计模式的研究和应用是以依赖倒转原则为指导原则的。

三种耦合关系

在面向对象的系统里,两个类之间可以发生三种不同的耦合关系:

l  零耦合关系:如果两个类没有耦合关系,则称为零耦合。

l  具体耦合关系:具体耦合发生在两个具体的类(可实例化的类)之间,经由一个类对另个一具体类的直接引用形成。

l  抽象耦合关系:抽象耦合关系发生在一个具体类和一个抽象类(或者Java接口)之间,使两个必须发生关系的类之间存有较大的灵活性。

变量的静态类型和真实类型

变量声明时的类型叫作变量的静态类型(Static Type),变量所引用的对象的实际类型叫作变量的真实类型(Actual Type)。比如:

Listemployees=new Vector();

其中,List是变量employees的静态类型,而它的实际类型是Vector。

三、Java对抽象类型的支持

在Java语言中,可以定义一种抽象类型,并且提供这一抽象类型的各种具体实现。Java语言提供了两种机制,Java接口和Java抽象类。

Java接口和Java抽象类的区别和特点

(1)      两者最明显的区别,在于Java抽象类可以提供某些方法的部分实现,而Java接口则不可以。这大概是Java抽象类唯一的优点。

如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就得到了这个新的具体方法,而Java接口做不到这一点。如果向Java接口加入一个新的方法的话,所有实现这个接口的类就不能全部成功的通过编译,因为他们没有实现这个新声明的方法。这是Java接口的一个缺点。

(2)      一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现类处在抽象类所定义出的继承的等级结构中,而由于Java语言限制一个类只能从最多一个超类继承,因此将抽象类作为类型定义工具的效能大大折扣。

对于Java接口,一个实现了一个Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口。这是Java接口和Java抽象类最重要的区别。此外,Java接口还具有其他的优越

性。

(3)      从代码重构的角度上讲,将一个单独的Java具体类重构成一个Java接口的实现是很容易的。只需要声明一个Java接口,并将重要的方法添加到接口声明中,然后在具体类定义语句后面加上一个合适的implements子句即可。

而为一个已有的具体类添加一个Java抽象类作为抽象类型却不容易,因为这个具体类可能已经有一个超类。这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新定义的抽象类必定处于整个类型等级结构的最上端,从而使等级结构中的所有成员都受到影响。

(4)      Java接口是定义混合类型的理想工具。所谓混合类型,就是在一个类的主要类型之外的次要类型。一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为。比如HashTable类就具有多个类型。它的主要类型是Map,这是一种Java聚集。而Cloneable接口和Serializable接口就是次要类型。

 

联合使用Java接口和Java抽象类

由于Java抽象类具有提供缺省实现的优点,而Java接口具有其他的所有优点,所有联合使用两者就是一个很好的选择。

首先,声明类型的工作仍然是由Java接口承担的,但是同时给出的还有一个Java抽象类,为这个接口给出一个缺省实现。其他同属于这个抽象类型的具体类可以有选择的实现这个Java接口,也可以选择继承自这个抽象类。

如果一个具体类直接实现这个Java接口的话,它就必须自行实现所有的接口;相反,如果一个具体类继承自Java抽象类的话,它就可以省去一些不必要的方法,因为它可以从抽象类中自动得到这些方法的缺省实现。

如果需要向Java接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法。

这其实就是缺省适配模式。在Java语言API中也是用了这种缺省适配模式,而且全都遵循一定的命名规范:Abstract+接口名。如接口Collection,抽象类名字是AbstractCollection;Map和AbstractMap,List与AbstractList等。

四、一个例子:账号、账号的种类和账号的状态

Account类有两个聚合关系,一个是AccountType,另一个是AccountStatus。这两个类型都是抽象类型,每一个抽象类型都有多于一个的具体实现。比如AccountType有Saving和Checking两个具体子类;而AccountStatus有Open和Overdrawn两种具体实现。类图如下。

 

Account类源代码。

public class Account {private AccountType accountType;private AccountStatus accountStatus;public Account(AccountType accountType) {//code}public void deposit(float amt) {//code}}

抽象类AccountType定义出所有具体子类必须实现的接口。

public abstract class AccountType {public abstract void deposit(float amt);}

抽象类AccountStatus定义出所有具体子类必须实现的接口。

public abstract class AccountStatus {public abstract void sendCorrespondence();}

Saving类是AccountType的具体子类,代表储蓄账号。

public class Saving extends AccountType{@Overridepublic void deposit(float amt) {//code}}

Checking类是AccountType的具体子类,代表支票账号。

public class Checking extends AccountType{@Overridepublic void deposit(float amt) {//code}}

Open是AccountStatus的具体子类,表示账号处于“开”状态。

public class Open extends AccountStatus{@Overridepublic void sendCorrespondence() {//code}}

Overdrawn是AccountStatus的具体子类,表示账号处于“超支”状态。

public class Overdrawn extends AccountStatus{@Overridepublic void sendCorrespondence() {//code}}

Account类依赖于AccountType和AccountStatus两个抽象类型,并不依赖于具体类,因此当由新的具体类型添加到系统中时,Account类不用改变。例如,如果系统引进了一种新型账号:MoneyMarket类型,Account类以及系统里面所有其他的依赖于AccountType抽象类的客户端都不需要改变。

MoneyMarket类的源代码。

public class MoneyMarket extends AccountType{@Overridepublic void deposit(float amt) {//code}}

注意:为了创建一个实例,必须直接调用此具体类的构造子。如果需要将一个具体类替换成为另一个具体类,而不改变创建此实例的方法,只有一个方法,那就是将创建责任下推给一个工厂类。

五、依赖倒转原则的优缺点

依赖倒转原则虽然很强大,但却是最不容易实现的。因为依赖关系倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。

此外,依赖倒转原则假定所有的具体类都是会变化的,这也不总是正确的。有一些具体类可能是相当稳定,不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类型,而不必为此发明一个抽象类型。

 

0 0
原创粉丝点击