和依赖的斗争
——谈谈使用模式前后的依赖关系变化
在面向对象的设计和编码过程中,类和类之间或多或少总有这样或那样的关系。除了继承和实现,其他的关系都可以归结到依赖这种关系里;所以说依赖是类之间最普遍的一种关系。而我们在设计和编码的时候,大部分时间都要和依赖打交道。可以说,一个类不可能不与其他类产生直接和间接的依赖关系,如果没有的话,那么这个类就失去了它存在的价值。但同时,值得注意的是,一个类不能与其他类产生过多的依赖关系;换句话说,一个类与其他类的依赖关系要尽量的少。这是面向对象的原则和模式一直试图向我们传输的一个原则。
可以说,一个类与其他类的依赖越少,那么这个模块或系统的复杂度就越低;这个模块或系统的可维护性和可扩展性就越高。这正是我们的设计和编码希望达到的。本文试图从依赖关系的角度来看看模式对依赖关系的影响,然后希望通过这种依赖的变化方向来指导我们的编码和设计;换句话说,不管我们的设计和编码是否是模式的,我们的类和类之间的关系都要符合这种依赖关系的变化。这样才是一个良好的设计和编码。
Dog dog = new Dog();
dog.bark();
我们可以看到,客户端类和Dog
类就有一种依赖关系。这种依赖关系是我们所需要的,不然Dog
类就对我们的客户类没有什么价值了。但我们同时也要注意到:这种依赖关系在系统的维护和扩展的时候,给我们的客户类带来的很多的麻烦。
例如,我们给系统增加一个Cat
类,那么客户类的调用可能变成了如下的样子:
if(condition1)
{
Dog dog = new Dog();
dog.bark();
}
else if(condition2)
{
Cat cat = new Cat();
cat.mew();
}
我们可以看到,系统的扩展对客户类是相当不利的,每当系统有扩展的时候,客户类就得不停的作相应的修改,来满足更多的扩展需要。我们可以说这样的系统的扩展性不好。下面,我们就要尝试着一步一步的把这个系统的扩展性设计得好起来。
我们首先想到的是,让这些Dog
类、Cat
类等都有一个公共的接口;然后如果系统有扩展,则新类都去实现这个接口,这是系统有了良好扩展性的基础。
public interface Animal
{
public void say();
}
这样,则Dog
类为:
public class Dog implements Animal
{
public void say()
{
……
}
}
Animal animal;
If(condition1)
{
animal = new Dog();
}
else
{
animal = new Cat();
}
animal.say();
现在我们看到,客户类的扩展性稍稍好了一些。分析一下各类之间的依赖关系,可以发现,客户类现在多了一个依赖:对接口的依赖。正是这个依赖关系使得该类的扩展性相好的方向发展。这就是模式所希望给我们带来的第一个依赖关系的变化:
1.
客户类要依赖于抽象,而不要依赖于具体
这个依赖的变化是系统扩展性的基础,以面向对象的基本原则:依赖颠倒原则来定义这个变化,它是系统的扩展性的基础。如上面的例子是一个工厂模式的开始。
绝大多数的模式都是以接口为基础的,我们都熟知和常用的命令模式、策略模式和状态模式都是建立在对行为、算法和状态的抽象的基础上的。从而实现了客户类从对具体的行为、算法和状态的依赖转移到对它们的抽象的依赖。下面以命令模式为例。
if(condition1)
{
//do action 1
……
}
else if(condition2)
{
//do action 2
……
}
else
{
//do action 3
……
}
这段代码依赖具体,可以很明显的看出这样的代码的扩展性不好。如果使用命令模式的话,我们首先要做的是对这些行为抽象出接口来,如:
public interface Action
{
public void doAction();
}
Action action;
if(condition1) action = new Action1();
else if(condition2) action = new Action2();
else action = new Action3();
action.doAction();
这就是一个使用了命令模式的客户类的初始样子。它转移了部分的依赖为对接口的依赖,从而使得客户类有了一定的扩展性。
而我们的组合模式却是将整体和部分抽象出统一的接口,从而使得对整体和部分的操作完成了统一。
//
如果条件为部分
if(…)
{
//
完成对部分的操作
……
}
//
如果条件为整体
else if(…)
{
//
完成对整体的操作
}
使用组合模式的话,我们为部分和整体做一个统一的接口:Component
,然后让部分和整体都去实现这个接口。客户类的调用如下:
Component part = new Part();
Component whole = new Whole();
whole.add(part);
//
然后对whole
对象进行操作,从而完成对part
对象的操作。
……
从以上的这些例子,模式一直所倡导的“对接口编程”其实就是想将客户类对具体的依赖转移到对抽象的依赖。只有把依赖从对具体的依赖转移到对抽象的依赖,我们才可以获得系统的扩展性。这是我们转移依赖的开始。
下面我们再回过头来看看我们在文章的开始就拿出来的那个猫狗的例子,我们通过对猫狗的抽象,已经得到如下的客户类的代码:
Animal animal;
If(condition1)
{
animal = new Dog();
}
else
{
animal = new Cat();
}
animal.say();
我们说这个客户类的扩展性仍然不是很好,因为客户类除了对抽象的接口有依赖关系,仍然对具体类,如Dog
类和Cat
类有依赖关系。这使得如果我们增加一个新类的话,客户类仍然需要做一定的修改。
如果要让客户类有良好的扩展性,必须消除掉这种对具体类,如Dog
类和Cat
类的依赖关系。工厂模式告诉我们,我们可以把客户类对具体类的依赖转移到工厂类里面去,这样彻底消除了客户类对具体类的依赖。这就是我们改变依赖关系的第二种方法:
2.
转移依赖
下面我们来看工厂模式是如何做的。首先是创建一个工厂类来生产各种不同的具体类,其代码如下:
public class Factory
{
public static Animal getInstance(String type)
{
if(type.equals(“dog”))
{
return new Dog();
@H_959_
403@
}