翻譯作者: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();
}
// rest of stampede simulator here
}
}

僅這樣只是得到了一點改善,因為代碼中還有兩個if else then子句。我們還可以進一步改進,我們來參數化工廠,用一個String來標示具體實例的類型:
僅這樣只是得到了一點改善,因為代碼中還有兩個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).
在我們繼續之前,我要指出靜態工廠(Static Factory)是一種經常被使用的超過真正的設計模式的慣用方法,但是象這樣使用的人常常用單詞“工廠(Factory)”來應用這個創建對象的方法. 無論如何,你能使用我們正要結束的靜態工廠或者仍何使用真正的工廠模式的技術(like the Factory Method or Abstract Factory patterns).
在我們繼續之前,我要指出靜態工廠(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
buffalo = headfirst.factory.simulator.Buffalo
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文件。
確認保存這個文件到目錄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