“围城”式困境中的依赖注入模式及Spring(3)
依赖注入模式和工厂模式
由于依赖注入模式的功能之一是初始化一个类,所以可以通过依赖注入模式来获取对象。我们知道,这个功能一向是工厂模式的领地。于是,有很多人就公然宣称:依赖注入模式能够代替工厂模式。他们的理由之一是,既然有了IOC容器,就可以通过它来初始化对象。何必再用工厂模式呢?因为如果用工厂模式的话,我们得多增加一个工厂类。
同时,IOC模式的狂热追随者宣称:工厂模式只能获得特定接口的类的实例;而IOC模式则可以获取任意类的对象。由此可以看出,IOC模式比工厂模式更加灵活。因此IOC模式可以取代工厂模式。
可事实真的是这样的吗?
假如我们有五种动物:Checken,Sheep,Cat,Dog,Pig;它们都继承Animal接口,如下:
public interface Animal
{
public void eat();
public String say();
}
其中,Checken类的实现如下:
public class Checken implements Animal
{
public void eat()
{
System.out.println(“The checken cats insert……”);
}
public String say()
{
System.out.println(“The checken’s saying sounds like crow……”);
return “crow”;
}
}
其他,关于Sheep类,Cat类,Dog类和Pig类的实现在这里不再给出,它们都实现了Animal接口。
如果我们用IOC容器来获取各个类的实例,这里以Spring的IOC容器为例,一般要经过如下的两步的配置:
以上是关于Checken类的配置,要完全实现上述的五个类的IOC容器的配置,还需要和上面雷同的四个配置。如下:
这样,这五个类的IOC容器的配置才算完成。上面的五个类如果用简单工厂模式来实现的话,代码如下:
public class Factory
{
public static Animal getInstance(String type)
{
if(“checken”.equals(type))
{
return new Checken();
}
else if(“sheep”.equals(type))
{
return new Sheep();
}
else if(“cat”.equals(type))
{
return new Cat();
}
else if(“dog”.equals(type))
{
return new Dog();
}
else
{
return new Pig();
}
}
}
把两者加以比较,可以看出,简单工厂模式的工厂类的编写,的确要比IOC容器的繁琐的配置要来得简单得多。
如此,我们可以看到使用IOC容器的第一个不便:繁琐复杂的配置管理,这样的配置既枯燥乏味,又容易出错。经常需要我们拷贝来拷贝去,但仍然保证不了不出错。
我们再来看一看我们是如何在IOC容器里取得实例的:
String[] contextFiles = null;
if(contextFiles == null)
{
contextFiles = new String[]{"applicationContext.xml",
"dataAccessContext-local.xml"};
}
appCtx = new ClassPathXmlApplicationContext(contextFiles);
Animal checken = (Animal)appCtx.getBean("checken");
而工厂模式又是如何取得各个类的实例的呢?
Animal checken = Factory.getInstance(“checken”);
我们可以看到,使用工厂模式取得类的实例非常简单,而使用IOC容器则由于容器的普适性,需要对取得的对象进行强制类型转化,以获得我们所需要的Animal类型。这是IOC容器的使用不如工厂模式方便的第二个方面。
第三个方面:所有的所谓的“插件式”编程所带来的同一个问题,IOC容器同样不能避免,当在工厂类我们误将Checken类的类名写错,如下:
if(“checken”.equals(type))
{
return new Check();
}
那么IDE在编译的时候是检查不出错误来的,只能是在运行的时候抛出ClassNotFoundException这样的违例来告诉我们出了错。很显然,这种查错方式不如编译的时候方便。
上面的讨论可以看出,依赖注入模式,或者说IOC容器在实现对依赖注入对象的普适性的时候,给我们引入了配置管理的麻烦。
而工厂模式则可以避免这些麻烦,所以工厂模式和依赖注入模式两者都有他们不同的适用范围。但是它们之间的区别又是非常微妙的,需要我们来细细的品位。
首先,如果产品是设计者能够把握的有限几种,如上面的Animal接口的几个实现,系统设计完成之后,可预期的产品扩展的可能性比较小。也就是除了上面的五个类以外,不大可能扩展出新的类来。这时候用工厂模式比较好,省去使用IOC容器所带来的配置麻烦。
除此之外,依赖注入模式和工厂模式还有一个重要的区别:就是工厂模式只关心生产产品,而对依赖的注入不太关心,或者说工厂模式对依赖注入的处理能力稍显不足。而依赖注入模式则强调了依赖的注入。两者的区别是工厂模式中变化的是产品,即有多种的产品;而依赖注入模式中变化的是依赖,即有千变万化的依赖。
就上面的例子,假如你只想听到各种动物的叫声,那么你只关心如何取得产品,则使用工厂模式比较方便,代码如下:
Animal animal = Factory.getInstance(type);
animal.say();
但是,如果你想找出像“bark”这样的叫声的动物来:
public class AnimalManager
{
private Animal animal;
public void setAnimal(Animal animal)
{
this.animal = animal;
}
public void findBarkAnimal()
{
if(this.animal.say.equals(“bark”))
{
System.out.println(animal.Class.getName()+” is a bark animal!”);
}
}
}
你的AnimalManager类可能给你无法控制的系统使用,那些系统可能有各种各样你所无法预知的动物。这时候使用依赖注入模式比较好。
依赖注入模式还有一个常用的地方:还是以上面的animal为例,如果我们在一个版本中只有一个Cat类,其配置如下。
我们对这个Animal的实现的使用如下:
//取得animal的对象
Animal animla = appCtx.getBean("animal");;
//对该对象进行操作
……
现在在下一个版本中,确定具体的Animal实现是要变化的,即肯定不是Cat对象了。假如是Dog对象。但是对Animal的实现的使用代码不变。
如果我们对Animal的使用的代码是这样的:
Animal animla = new Cat();
……
那么当我们在下一个版本需要将Cat换成Dog的话,就不得不上面的对Animal的使用代码了。
当然,你可能会说,我使用工厂模式也能做到啊。
不错,当你的系统只有一个Animal接口的时候,你使用工厂模式也可以。但如果你的系统有很多的接口,它们的实现遇到的情况也和Animal接口遇到的情况一模一样,那么你使用工厂模式还好吗?你将不得不为每一个接口做一个工厂。
啰里啰唆地说了好多,现在在总结一下吧,以此作为这一节文字的蛇足吧!
第一、
使用工厂模式的情况,工厂类的设计者可以确定有多少个产品,而且产品的扩展性不大,就是说不太可能有新的产品会添加到已经设计好的系统中。
第四、 一个系统里有很多的接口,而系统的不同的版本对同一个接口的实现的使用是相同的,而不同的版本的实现是不同的。这时候也应该使用依赖注入模式。