打破最后的依赖-Head First Design Patterns对工厂的解释


翻译作者: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 DriveI'm a BuffaloI'm a HorseI'm a CowboyI'm a Cowgirl

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结