翻译作者:zming
翻译自:http://today.java.net/pub/a/today/2005/04/14/dependency.html
转载请注明出处:http://blog.csdn.net/zmxj/archive/2005/05/25/380784.aspx
<<Head First Design Patterns>>一书的Factory 模式章节中,建议我们要“Breaking the Last Dependency”,即打破最后的依赖,并且展示了如何写出完全远离具体类的代码。下面我们来看看这个主题。
看看breaking the last dependency 是什么意思?它是如何来描述工厂模式的?以及我们为什么应该关注它?所有的工厂模式都是封装具体类的实例并帮助你将代码和具体类的依赖减少到最少。看下面的代码:
public class StampedeSimulator {
Actor actor = null;
public void addActor(String type) {
if (type.equals("buffalo")) {
actor = new Buffalo();
} else if (type.equals("horse")) {
actor = new Horse();
} else if (type.equals("cowboy")) {
actor = new Cowboy();
} else if (type.equals("cowgirl")) {
actor = new Cowgirl();
}
// rest of simulator here
}
}
这段代码中包含了四个不同的具体类(Buffalo, Horse, Cowboy, and Cowgirl),结果他建立了依赖关系在你的代码和这些具体类之间,这为什么是一件坏事呢?你想想,如果你要加入一个新的类型(比如Coyote)或者重新配置具体类(比如你想用FastHorse类替代普通的Horse类),你将重新修改你的代码,这造成难维护性。切记,可能类似的代码会遍布你的所有代码中,如果你要修改这个代码需要到多处修改。注意我们不要寄希望于Java5.0的enumerations匹配字符串来减少这些代码,不是所有的用户都可以在Java5平台下的(比如苹果系统的用户),我们将作其他的实践。
现在我们有没有一个好的方法减少具体类的依赖呢?那将使你的生活更加轻松,减少你大量的代码维护工作,办法就是使用Factory.
有几种类型的工厂,用哪一种你可以查相关的模式书。为了我们的事例,让我们看看Static Factory,它由一个类组成,它提供一个静态方法来操纵一个对象的实例。要实现这个,我们将所有实例代码放到一个factory里,ActorFactory,替换上面StampedeSimulator代码,用factory来创建对象:
public class ActorFactory {
static public Actor createBuffalo() {
return new Buffalo();
}
static public Actor createHorse() {
return new Horse();
}
static public Actor createCowboy() {
return new Cowboy();
}
static public Actor createCowgirl() {
return new Cowgirl();
}
}
And we can alter our StampedeSimulator to look like this:
public class StampedeSimulator {
Actor actor = null;
public void addActor(String type) {
if (type.equals("buffalo")) {
actor = ActorFactory.createBuffalo();
} else if (type.equals("horse")) {
actor = ActorFactory.createHorse();
} else if (type.equals("cowboy")) {
actor = ActorFactory.createCowboy();
} else if (type.equals("cowgirl")) {
actor = ActorFactory.createCowgirl();
}
仅这样只是得到了一点改善,因为代码中还有两个if else then子句。我们还可以进一步改进,我们来参数化工厂,用一个String来标示具体实例的类型:
public class ActorFactory {
static public Actor createActor(String type) {
if (type.equals("buffalo")) {
return new Buffalo();
} else if (type.equals("horse")) {
return new Horse();
} else if (type.equals("cowboy")) {
return new Cowboy();
} else if (type.equals("cowgirl")) {
return new Cowgirl();
} else {
return null;
}
}
}
public class StampedeSimulator {
Actor actor = null;
public void addActor(String type) {
actor = ActorFactory.createActor(type);
// rest of stampede simulator here
}
}
现在我们已经很好分离了具体类和我们的代码中的依赖。注意,工厂中的方法的返回类型是一个接口(Actor)或者也可以是一个抽象类。这使得你的客户端不需要知道具体的类是什么,因而,在你的客户端代码里使用接口,你将继续解耦和你的具体类的依赖。静态工厂创建你需要的对象,你的客户端代码不需要担心它。现在,如果你需要改变代码,你只需要去一个地方,实例都被封装了。
这样把具体类封装到工厂中是很好的事,我们解耦了主要代码和具体类之间的依赖。但是工厂本身仍然依赖于具体的类,如果我们需要改变那些类,就是说需要修改工厂的代码,重新编译,那样不是我们想要做的,我们希望移除所有这样的依赖在我们的代码里。
在我们继续之前,我要指出静态工厂(Static Factory)是一种经常被使用的超过真正的设计模式的惯用方法,但是象这样使用的人常常用单词“工厂(Factory)”来应用这个创建对象的方法. 无论如何,你能使用我们正要结束的静态工厂或者仍何使用真正的工厂模式的技术(like the Factory Method or Abstract Factory patterns).
Let's Break that Last Dependency
(让我们打破最后的依赖) 我们解耦了应用主要代码和具体类的依赖,但是Static Factory, ActorFactory仍然牢牢地绑定着具体的类,加之丑陋的if-then-else语句仍然存在。我们如何才能改善这些移除最后的依赖呢?
有一种技术是使用java的Class.forName()。forName()方法允许你用指定的包路径下的类名动态的装入类。一旦你要取得类,你只需要用实例化一个它的新实例,并且返回它。 让我们看他怎样工作:
class ActorFactory {
static public Actor createActor(String type) {
Actor actor = null;
Class actorClass = null;
try {
actorClass = Class.forName(type);
} catch (ClassNotFoundException e) {
System.out.println("Error: class " + type + " not found.");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
if (actorClass != null) {
try {
actor = (Actor) actorClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
return actor;
}
}
这个代码更加解耦了你的应用和具体类的依赖,因为现在你可以通过传递类名(或至少一些实现了Actor接口的类)给工厂,你就可以取得类的实例。我们为此付出的代价就是我们不得不检测所有可能的途径:首先,确信我们传递的类名字串的类事实存在,并且确信你能够实例化这个类,我们可以在这偷个懒,我们可以在不能装入或实例化一个类而发生异常时,打印出异常的stacktrace,在实际应用中,显然你不得不做的更多。我们也用灵活性换取了少许对静态类型检测的控制。你将要通过稍微的思考,对于实例,它能够完美的合法的为我们装入Actor类,但是我们不能实际上从Actor实例一个对象,因为他是一个接口。
一旦我们修改了ActorFactory,我们需要在你的应用代码里做一些小的修改,我们需要传递由String描述的actor类。像这样:
simulator.addActor("headfirst.factory.simulator.Buffalo");
simulator.addActor("headfirst.factory.simulator.Horse");
simulator.addActor("headfirst.factory.simulator.Cowboy");
simulator.addActor("headfirst.factory.simulator.Cowgirl");
像这样,我们能够编译和运行这个代码并且和先前得到相同的结果:每一个actor类型被实例化了。
现在,当我们想要改变stampede simulator的actors时(例如,我们要拍一个电影,用动画的演员替换真实的演员),所有要做的就是改变我们传递给addActor()方法的描述actor类型的String串即可。我们根本不需要改变ActorFactory or StampedeSimulator中的任何代码。
Taking It All the Way
这是一个改进,但是代码仍然和在actors的指定类型偶合,我们仍然需要指定在代码中和传递给addActor()方法的Actor 类型的名字,意思就是当我们要改变演员的时候不得不重新编译代码,有什么其他的方法取得演员的类型,而没有代码依赖我们想要的演员的类型吗?
有一个办法就是我们删除所有依赖具体类型的代码,指定我们想要的actors的类型在一个properties文件,在运行时装入他们。这样我们就没有依赖具体演员类型的代码了。这样做,我们改变指定的演员类型。替换硬编码actor类型,用编码载入类型从一个叫做actor.properties的properties文件。这个文件每行是你需要的一个演员类型,看起来像这样:
buffalo = headfirst.factory.simulator.Buffalo
horse = headfirst.factory.simulator.Horse
cowboy = headfirst.factory.simulator.Cowboy
cowgirl = headfirst.factory.simulator.Cowgirl
这是一个标准格式的java properties文件:等号两边分别是属性名和属性值。现在可以替换传递给createActor()方法的actor的类型的完整路径名,我们只要传递一个描述类型的串给他(就象我们的第一个版本中代码那样),这个串将对应于properties文件中的属性名:
simulator.addActor("buffalo");
simulator.addActor("horse");
simulator.addActor("cowboy");
simulator.addActor("cowgirl");
我们同样需要修改ActorFactory的createActor()方法,从properties文件中装入所有的属性到一个Properties实例中。然后传递类型给createActor()方法(例如:”buffalo”),取得属性对应的actor的完整类型名,并用它实例化成我们需要的actor对象。
static public Actor createActor(String type) {
Class actorClass = null;
Actor actor = null;
String actorType = null;
Properties properties = new Properties();
try {
properties.load(new FileInputStream("simulator.properties"));
} catch (IOException e) {
System.out.println("Error: couldn't read from the simulator.properties file."
+ e.getMessage());
}
actorType = properties.getProperty(type);
if (actorType == null || actorType.equals("")) {
System.out.println("Error loading actor type for type: " + type);
}
try {
actorClass = Class.forName(actorType);
} catch (ClassNotFoundException e) {
System.out.println("Error: class " + actorType + " not found!");
System.out.println(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
if (actorClass != null) {
try {
actor = (Actor) actorClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
return actor;
}
你当然可以添加属性来指定添加多少的类型到simulator,这样最好了。
现在你可以不需要指定任何的actor具体类在你代码的任何地方,你已经完全的解耦了。
概要 不同的工厂模式的目的是减少依赖具体的类。我们一步步进展并明白了如何移除最后的依赖。首先,我们将具体实例对象的代码移到我们的主要代码之外,将它放到一个工厂里。然后我们在这个基础上改进它,再将路径名和类名传递给工厂的基础上,使它动态地装入具体的类和实例化他们,这仅仅是必须确保每一个传递来得类都实现了工厂的返回接口。最后,我们打破了最后的依赖,从properties文件装入我们想要的类型到simulator。这使我们完全的消除了与具体类的依赖。
记住,当你减少依赖的时候,你不需保证你的代码的健壮性、可维护性、扩展性。
完整的代码
如果你想试一下这个程序,你可以拷贝下面的代码到下一个文件,
StampedeSimulatorTestDrive.java:
package headfirst.factory.simulator;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class StampedeSimulatorTestDrive {
public static void main(String[] args) {
System.out.println("Stampede Test Drive");
StampedeSimulator simulator = new StampedeSimulator();
simulator.addActor("buffalo");
simulator.addActor("horse");
simulator.addActor("cowboy");
simulator.addActor("cowgirl");
}
}
class StampedeSimulator {
public void addActor(String type) {
Actor actor = null;
actor = ActorFactory.createActor(type);
actor.display();
// rest of stampede simulator here
}
}
class ActorFactory {
static public Actor createActor(String type) {
Class actorClass = null;
Actor actor = null;
String actorType = null;
Properties properties = new Properties();
try {
properties.load(new FileInputStream("simulator.properties"));
} catch (IOException e) {
System.out.println("Error: couldn't read from the simulator.properties file."
+ e.getMessage());
}
actorType = properties.getProperty(type);
if (actorType == null || actorType.equals("")) {
System.out.println("Error loading actor type for type: " + type);
}
try {
actorClass = Class.forName(actorType);
} catch (ClassNotFoundException e) {
System.out.println("Error: class " + actorType + " not found!");
System.out.println(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
if (actorClass != null) {
try {
actor = (Actor) actorClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
return actor;
}
}
interface Actor {
public void display();
}
class Buffalo implements Actor {
public void display() {
System.out.println("I'm a Buffalo");
}
}
class Horse implements Actor {
public void display() {
System.out.println("I'm a Horse");
}
}
class Cowboy implements Actor {
public void display() {
System.out.println("I'm a Cowboy");
}
}
class Cowgirl implements Actor {
public void display() {
System.out.println("I'm a Cowgirl");
}
}
确认保存这个文件到目录src/headfirst/factory/simulator.(如果你已经下载了Head First Design Patterns中的代码code,你就已经有了src/headfirst/factory目录,只要新建一个simulator目录在factory目录就可以了)创建一个class目录保存你的class文件。
不要忘了创建一个simulator.properties文件,包括你的属性项(这个文件是最重要的):
buffalo = headfirst.factory.simulator.Buffalo
horse = headfirst.factory.simulator.Horse
cowboy = headfirst.factory.simulator.Cowboy
cowgirl = headfirst.factory.simulator.Cowgirl
现在可以编译、运行代码象下面:
javac -d ./classes ./src/headfirst/factory/simulator/StampedeSimulatorTestDrive.java
java -cp ./classes headfirst.factory.simulator.StampedeSimulatorTestDrive
你可以看到下面的输出:
Stampede Test Drive
I'm a Buffalo
I'm a Horse
I'm a Cowboy
I'm a Cowgirl