翻譯作者:zming
翻譯自:http://today.java.net/pub/a/today/2005/04/14/dependency.html
轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/zmxj/archive/2005/05/25/380784.aspx
<<Head First Design Patterns>>一書(shū)的Factory 模式章節(jié)中,建議我們要“Breaking the Last Dependency”,即打破最后的依賴,并且展示了如何寫出完全遠(yuǎn)離具體類的代碼。下面我們來(lái)看看這個(gè)主題。
看看breaking the last dependency 是什么意思?它是如何來(lái)描述工廠模式的?以及我們?yōu)槭裁磻?yīng)該關(guān)注它?所有的工廠模式都是封裝具體類的實(shí)例并幫助你將代碼和具體類的依賴減少到最少。看下面的代碼:
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
}
}
這段代碼中包含了四個(gè)不同的具體類
(Buffalo
, Horse
, Cowboy
, and Cowgirl
),結(jié)果他建立了依賴關(guān)系在你的代碼和這些具體類之間,這為什么是一件壞事呢?你想想,如果你要加入一個(gè)新的類型(比如Coyote)或者重新配置具體類(比如你想用FastHorse類替代普通的Horse類),你將重新修改你的代碼,這造成難維護(hù)性。切記,可能類似的代碼會(huì)遍布你的所有代碼中,如果你要修改這個(gè)代碼需要到多處修改。注意我們不要寄希望于Java5.0的enumerations匹配字符串來(lái)減少這些代碼,不是所有的用戶都可以在Java5平臺(tái)下的(比如蘋果系統(tǒng)的用戶),我們將作其他的實(shí)踐。
現(xiàn)在我們有沒(méi)有一個(gè)好的方法減少具體類的依賴呢?那將使你的生活更加輕松,減少你大量的代碼維護(hù)工作,辦法就是使用Factory.有幾種類型的工廠,用哪一種你可以查相關(guān)的模式書(shū)。為了我們的事例,讓我們看看Static Factory,它由一個(gè)類組成,它提供一個(gè)靜態(tài)方法來(lái)操縱一個(gè)對(duì)象的實(shí)例。要實(shí)現(xiàn)這個(gè),我們將所有實(shí)例代碼放到一個(gè)factory里,ActorFactory,替換上面StampedeSimulator
代碼,用factory來(lái)創(chuàng)建對(duì)象:
























































僅這樣只是得到了一點(diǎn)改善,因?yàn)榇a中還有兩個(gè)if else then子句。我們還可以進(jìn)一步改進(jìn),我們來(lái)參數(shù)化工廠,用一個(gè)String來(lái)標(biāo)示具體實(shí)例的類型:
僅這樣只是得到了一點(diǎn)改善,因?yàn)榇a中還有兩個(gè)if else then子句。我們還可以進(jìn)一步改進(jìn),我們來(lái)參數(shù)化工廠,用一個(gè)String來(lái)標(biāo)示具體實(shí)例的類型:
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
}
}
現(xiàn)在我們已經(jīng)很好分離了具體類和我們的代碼中的依賴。注意,工廠中的方法的返回類型是一個(gè)接口(Actor
)或者也可以是一個(gè)抽象類。這使得你的客戶端不需要知道具體的類是什么,因而,在你的客戶端代碼里使用接口,你將繼續(xù)解耦和你的具體類的依賴。靜態(tài)工廠創(chuàng)建你需要的對(duì)象,你的客戶端代碼不需要擔(dān)心它。現(xiàn)在,如果你需要改變代碼,你只需要去一個(gè)地方,實(shí)例都被封裝了。
這樣把具體類封裝到工廠中是很好的事,我們解耦了主要代碼和具體類之間的依賴。但是工廠本身仍然依賴于具體的類,如果我們需要改變那些類,就是說(shuō)需要修改工廠的代碼,重新編譯,那樣不是我們想要做的,我們希望移除所有這樣的依賴在我們的代碼里。
在我們繼續(xù)之前,我要指出靜態(tài)工廠(Static Factory)是一種經(jīng)常被使用的超過(guò)真正的設(shè)計(jì)模式的慣用方法,但是象這樣使用的人常常用單詞“工廠(Factory)”來(lái)應(yīng)用這個(gè)創(chuàng)建對(duì)象的方法. 無(wú)論如何,你能使用我們正要結(jié)束的靜態(tài)工廠或者仍何使用真正的工廠模式的技術(shù)(like the Factory Method or Abstract Factory patterns).
在我們繼續(xù)之前,我要指出靜態(tài)工廠(Static Factory)是一種經(jīng)常被使用的超過(guò)真正的設(shè)計(jì)模式的慣用方法,但是象這樣使用的人常常用單詞“工廠(Factory)”來(lái)應(yīng)用這個(gè)創(chuàng)建對(duì)象的方法. 無(wú)論如何,你能使用我們正要結(jié)束的靜態(tài)工廠或者仍何使用真正的工廠模式的技術(shù)(like the Factory Method or Abstract Factory patterns).
在我們繼續(xù)之前,我要指出靜態(tài)工廠(Static Factory)是一種經(jīng)常被使用的超過(guò)真正的設(shè)計(jì)模式的慣用方法,但是象這樣使用的人常常用單詞“工廠(Factory)”來(lái)應(yīng)用這個(gè)創(chuàng)建對(duì)象的方法. 無(wú)論如何,你能使用我們正要結(jié)束的靜態(tài)工廠或者仍何使用真正的工廠模式的技術(shù)(like the Factory Method or Abstract Factory patterns).
Let's Break that Last Dependency
(讓我們打破最后的依賴)
我們解耦了應(yīng)用主要代碼和具體類的依賴,但是Static Factory, ActorFactory
仍然牢牢地綁定著具體的類
,
加之丑陋的
if-then-else
語(yǔ)句仍然存在。我們?nèi)绾尾拍芨纳七@些移除最后的依賴呢?
有一種技術(shù)是使用
java
的
Class.forName()
。
forName()
方法允許你用指定的包路徑下的類名動(dòng)態(tài)的裝入類。
一旦你要取得類,你只需要用實(shí)例化一個(gè)它的新實(shí)例,并且返回它。 讓我們看他怎樣工作:
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;
}
}
這個(gè)代碼更加解耦了你的應(yīng)用和具體類的依賴,因?yàn)楝F(xiàn)在你可以通過(guò)傳遞類名(或至少一些實(shí)現(xiàn)了Actor接口的類)給工廠,你就可以取得類的實(shí)例。我們?yōu)榇烁冻龅拇鷥r(jià)就是我們不得不檢測(cè)所有可能的途徑:首先,確信我們傳遞的類名字串的類事實(shí)存在,并且確信你能夠?qū)嵗@個(gè)類,我們可以在這偷個(gè)懶,我們可以在不能裝入或?qū)嵗粋€(gè)類而發(fā)生異常時(shí),打印出異常的stacktrace,在實(shí)際應(yīng)用中,顯然你不得不做的更多。我們也用靈活性換取了少許對(duì)靜態(tài)類型檢測(cè)的控制。你將要通過(guò)稍微的思考,對(duì)于實(shí)例,它能夠完美的合法的為我們裝入Actor類,但是我們不能實(shí)際上從Actor實(shí)例一個(gè)對(duì)象,因?yàn)樗且粋€(gè)接口。
一旦我們修改了ActorFactory,我們需要在你的應(yīng)用代碼里做一些小的修改,我們需要傳遞由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");
像這樣,我們能夠編譯和運(yùn)行這個(gè)代碼并且和先前得到相同的結(jié)果:每一個(gè)actor類型被實(shí)例化了。
現(xiàn)在,當(dāng)我們想要改變stampede simulator的actors時(shí)(例如,我們要拍一個(gè)電影,用動(dòng)畫(huà)的演員替換真實(shí)的演員),所有要做的就是改變我們傳遞給addActor()方法的描述actor類型的String串即可。我們根本不需要改變ActorFactory
or StampedeSimulator
中的
任何代碼。
Taking It All the Way
這是一個(gè)改進(jìn),但是代碼仍然和在actors的指定類型偶合,我們?nèi)匀恍枰付ㄔ诖a中和傳遞給addActor()方法的Actor 類型的名字,意思就是當(dāng)我們要改變演員的時(shí)候不得不重新編譯代碼,有什么其他的方法取得演員的類型,而沒(méi)有代碼依賴我們想要的演員的類型嗎?
有一個(gè)辦法就是我們刪除所有依賴具體類型的代碼,指定我們想要的actors的類型在一個(gè)properties文件,在運(yùn)行時(shí)裝入他們。這樣我們就沒(méi)有依賴具體演員類型的代碼了。這樣做,我們改變指定的演員類型。替換硬編碼actor類型,用編碼載入類型從一個(gè)叫做actor.properties的properties文件。這個(gè)文件每行是你需要的一個(gè)演員類型,看起來(lái)像這樣:
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
這是一個(gè)標(biāo)準(zhǔn)格式的
java properties
文件:等號(hào)兩邊分別是屬性名和屬性值。現(xiàn)在可以替換傳遞給
createActor()
方法的
actor
的類型的完整路徑名,我們只要傳遞一個(gè)描述類型的串給他(就象我們的第一個(gè)版本中代碼那樣),這個(gè)串將對(duì)應(yīng)于
properties
文件中的屬性名:
simulator.addActor("buffalo");
simulator.addActor("horse");
simulator.addActor("cowboy");
simulator.addActor("cowgirl");
我們同樣需要修改
ActorFactory
的
createActor()
方法,從
properties
文件中裝入所有的屬性到一個(gè)
Properties
實(shí)例中。然后傳遞類型給
createActor()
方法
(
例如:
”buffalo”),
取得屬性對(duì)應(yīng)的
actor
的完整類型名,并用它實(shí)例化成我們需要的
actor
對(duì)象。
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;
}
你當(dāng)然可以添加屬性來(lái)指定添加多少的類型到simulator,這樣最好了。
現(xiàn)在你可以不需要指定任何的actor具體類在你代碼的任何地方,你已經(jīng)完全的解耦了。
概要
不同的工廠模式的目的是減少依賴具體的類。我們一步步進(jìn)展并明白了如何移除最后的依賴。首先,我們將具體實(shí)例對(duì)象的代碼移到我們的主要代碼之外,將它放到一個(gè)工廠里。然后我們?cè)谶@個(gè)基礎(chǔ)上改進(jìn)它,再將路徑名和類名傳遞給工廠的基礎(chǔ)上,使它動(dòng)態(tài)地裝入具體的類和實(shí)例化他們,這僅僅是必須確保每一個(gè)傳遞來(lái)得類都實(shí)現(xiàn)了工廠的返回接口。最后,我們打破了最后的依賴,從properties文件裝入我們想要的類型到simulator。這使我們完全的消除了與具體類的依賴。
記住,當(dāng)你減少依賴的時(shí)候,你不需保證你的代碼的健壯性、可維護(hù)性、擴(kuò)展性。
完整的代碼
如果你想試一下這個(gè)程序,你可以拷貝下面的代碼到下一個(gè)文件,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");
}
}
確認(rèn)保存這個(gè)文件到目錄src/headfirst/factory/simulator.(如果你已經(jīng)下載了Head First Design Patterns中的代碼code,你就已經(jīng)有了src/headfirst/factory目錄,只要新建一個(gè)simulator目錄在factory目錄就可以了)創(chuàng)建一個(gè)class目錄保存你的class文件。
確認(rèn)保存這個(gè)文件到目錄src/headfirst/factory/simulator.(如果你已經(jīng)下載了Head First Design Patterns中的代碼code,你就已經(jīng)有了src/headfirst/factory目錄,只要新建一個(gè)simulator目錄在factory目錄就可以了)創(chuàng)建一個(gè)class目錄保存你的class文件。
不要忘了創(chuàng)建一個(gè)simulator.properties文件,包括你的屬性項(xiàng)(這個(gè)文件是最重要的):
buffalo = headfirst.factory.simulator.Buffalo
horse = headfirst.factory.simulator.Horse
cowboy = headfirst.factory.simulator.Cowboy
cowgirl = headfirst.factory.simulator.Cowgirl
現(xiàn)在可以編譯、運(yùn)行代碼象下面:
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