2015-06-18 116 views
2

我最近開始研究一個Java項目,該項目已經有了一個團隊在3個月內開發的大規模代碼庫。我注意到在許多地方,他們直接在客戶端對象的構造函數中實例化一些對象,而不是使用依賴注入。我想將對象構造重構爲工廠,並使用一些注入框架。Java對象創建模式和設計

我已經創建了一個工廠,基本上是一個工作班次new <type(some params here)>。這裏沒有什麼特別的東西 - 沒有單身,沒有靜態的工廠模式。只是返回一個新的依賴關係實例的newInstance()方法。

在代碼中顯示的內容:

class A { A() { 
     B bobj = new B(); // A and B are coupled directly 
    } 
} 

我想重構這個到:

BFactory { 
    newInstance() { return new B(); // return B implementation } 
} 

class A { 
    A(BFactory factory){ 
    B bobj = factory.newInstance(); // A does not know about B impl 
    } 
} 

我的論點是對象不應該在任何地方在代碼中創建除了在工廠意味着那個目的。這會促進鬆散耦合,否則你會緊密耦合這兩種類型。一位高級會員(我試圖重構的代碼的作者)認爲,單線工廠是一個過於複雜的設計。

是否存在有關處理此問題的模式的權威性建議/參考?什麼可以用來決定哪種方法更好,爲什麼呢?

+2

我的論點是「如果它沒有損壞,不要修復它。」使用工廠方法獲得的損失耦合不值得花時間或風險(錯誤),它需要觸及代碼庫的每個部分。現在只需滾動它。 – markspace

+0

你可以發表一個簡短的例子來說明你正在談論的事情嗎?你的描述可以被解釋爲幾種不同的方式。 – cyroxis

+2

我會稍微將@ markspace的評論更改爲「等到它壞了才解決它。」。如果當你需要進行一次改變,那麼使用工廠方法將會變得更加簡單和清潔,那麼就需要對該類進行重構。如果某件事正在發揮作用並且不需要改變,那就讓它一個人待着 –

回答

1

一位高級會員(我試圖重構的代碼的作者)認爲,單線工廠是過於複雜的設計。

這看起來像是你問題的癥結,而不是你是否應該重構代碼,所以讓我們回答它,而不是偏離實際問題。如果我們考慮您在代碼中提供的示例,我同意您的同事。您不應該爲要注入的每個依賴項創建一個工廠類。你試圖達到的目標沒有什麼不對,但你試圖達到目標的方式是過度的。

您或者依賴於Factory類的層次結構,該類知道如何創建每個依賴關係,或者您依賴於實際的類本身,並具有可將對象連接在一起的Container

選項1:依靠一個共同的工廠

class A { 
    B bobj; 
    C cobj; 
    A(Factory factory){ 
    bobj = factory.createB(); 
    cobj = factory.createC(); 
    } 
} 

選項2:依靠依賴直接

class A { 
    B bobj; 
    C cobj; 
    A(A a,B b) { 
     this.bobj = b; 
     this.cobj = c 
    } 
} 

然後,您可以如何電線創建一個Container類,知道物體在一起:

class Container { 
    public static B createB() { 
     return new BImpl(); 
    } 

    public static C createC() { 
     return new CImpl(); 
    } 

    public static A createA() { 
     return newAImpl(createB(),createC()); 
    } 
} 

上面介紹的例子太基本了。在現實世界中,你將主要有一個更復雜的依賴關係圖。這就是DI框架派上用場而不是重新發明輪子的地方。如果您的最終目標是開始使用DI框架,則可以使用選項2,因爲DI框架通過向客戶端提供依賴關係而不是客戶端代碼請求來實現控制反轉。

0

你的基礎點是完全有效的,在構造函數中直接實例化對象通常不是一個好主意(它們可能是該規則的有效例外)。

如果你這樣做:

class Car { 

    private Engine engine; 

    public Car() { 
     engine = new Engine(); 
    } 

} 

你將很難一次測試時沒有汽車發動機。你必須使用反射才能通過模擬交換實例。

如果做以下代替

class Car { 

    private Engine engine; 

    @Inject 
    public Car(Engine engine) { 
     this.engine = engine; 
    } 

} 

它是很容易用假引擎來測試汽車,更換髮動機的實施或改變發動機應該構造方式(例如,添加更多的參數構造函數)。

但是,你應該肯定使用已建立的依賴注入框架,而不是編寫自己的工廠。如果你通過一個「共同工廠」,Chetan建議你最終隱藏你的依賴關係。

使用依賴注入更多的動力很好的資源可以在這裏找到: https://github.com/google/guice/wiki/Motivationhttps://youtu.be/acjvKJiOvXw(非常好說話,應該是值得你花時間)。

+0

爲什麼不從這兩個類都有兩種類型的構造函數,一個是默認提供引擎然後其他可以讓你注入一個引擎。 – AlexC

+0

@AlexC當你的一個客戶的'DefaultEngine'不再是'DefaultEngine'的時候會發生什麼?如果客戶不知道他們想要什麼樣的引擎,那麼'Factory'理想情況下應該注入一個應該作爲默認引擎的引擎。對於一個客戶端來說,'Engine1'可能是默認引擎,另一個客戶端可能是'Engine2'。 「車」真的很在乎嗎?答案是否定的,因爲藐視控制倒置的目的。 – CKing

+0

很多假設。你制定了一個用例,現在你正在制定限制並回答你自己的問題。如果包含默認設置的IoC配置不再是以前的版本,該怎麼辦?代碼可能過時,所以可以配置文件。再次閱讀我的答案,你可以同時擁有默認行爲和IoC,你的觀點到底是什麼? – AlexC