2011-10-18 35 views
5

在C++中,我經常使用RAII風格的對象來使代碼更加可靠,並將它們分配到堆棧上以使代碼更具性能(並避免bad_alloc)。堆棧分配的RAII對象與DI原理

但是在堆棧上創建一個具體類的對象違反了依賴倒置(DI)原則並且防止嘲笑這個對象。

考慮下面的代碼:

struct IInputStream 
{ 
    virtual vector<BYTE> read(size_t n) = 0; 
}; 

class Connection : public IInputStream 
{ 
public: 
    Connection(string address); 
    virtual vector<BYTE> read(size_t n) override; 
}; 

struct IBar 
{ 
    virtual void process(IInputStream& stream) = 0; 
}; 

void Some::foo(string address, IBar& bar) 
{ 
    onBeforeConnectionCreated(); 
    { 
     Connection conn(address); 
     onConnectionCreated(); 
     bar.process(conn); 
    } 
    onConnectionClosed(); 
} 

我可以測試IBar::process,但我也想測試Some::foo,而無需創建真正的Connection對象。

當然,我可以使用工廠,但它會使代碼顯着複雜化並引入堆分配。
此外,我不喜歡添加Connection::open方法,我更喜歡構造完全初始化和功能齊全的對象。

我會做Connection類型的模板參數Some(或foo如果提取出來作爲一個免費的功能),但我不知道這是正確的方式(模板看起來像一個黑色的魔法很多人,所以我更喜歡使用動態多態)

+2

模板不應該對於或多或少能勝任的C++程序員來說是魔法,我沒有理由避免它們。另外我不認爲堆分配是昂貴的(這當然取決於你編寫的軟件),所以我沒有理由避免它(當用於智能指針時)。 –

+4

@亞歷B:有種理由避免它們,儘管我同意這不是因爲它們是「黑魔法」。這是因爲如果所有東西都是通過模板參數注入的,那麼您編寫的所有東西都是模板,您的庫只是標頭,並且在編譯或分發方面可能會非常麻煩。儘管如此,我想小心你可以單元測試只有頭文件的庫,然後從它構建一個只包含應用程序需要的實例的TU。 –

+1

RAII和DI在一起工作很好,所以標題是誤導性的,你的問題是堆棧分配與DI。 –

回答

5

你現在正在做的是「強制耦合」RAII類和服務提供者類(如果你想要可測試性,應該是一個真正的接口)。通過解決這個問題:

  1. 抽象ConnectionIConnection
  2. 有一個單獨的ScopedConnection類,它提供RAII重要的是

頂部例如:

void Some::foo(string address, IBar& bar) 
{ 
    onBeforeConnectionCreated(); 
    { 
     ScopedConnection conn(this->pFactory->getConnection()); 
     onConnectionCreated(); 
     bar.process(conn); 
    } 
    onConnectionClosed(); 
} 
+2

並且接受'ScopedConnection'不需要被模擬,即使在應該隔離'Some :: foo'的測試中使用真實版本也是「安全的」。或者如果這是不可接受的,那就咬緊牙關,把它作爲模板參數注入,或者使用'scoped_ptr'來提供RAII,作爲一個標準類(或者第三方,如果你仍然使用C++ 03)是一個可以接受的難題依賴。 –

+0

這就是我寫關於工廠的內容。爲了遵循你的回答,我應該爲Connection創建一個工廠,或者爲很多不相關的類創建工廠(如你所建議的)。通過很多層把這個工廠帶到'Some'(或者讓它成爲全球的)。 – Abyx

+0

@Abyx:工廠將成爲DI的主要候選人,這比通過手動或全局傳遞更好。但是你需要它來增加抽象。 – Jon

1

通過「我可以使用一個工廠,但它會顯着複雜的代碼和引入堆分配「我的意思是以下步驟:

創建抽象類,並從它

struct AConnection : IInputStream 
{ 
    virtual ~AConnection() {} 
}; 

添加工廠方法Some

class Some 
{ 
..... 
protected: 
    VIRTUAL_UNDER_TEST AConnection* createConnection(string address); 
}; 

獲得Connection通過智能指針

unique_ptr<AConnection> conn(createConnection(address)); 
1

您的實際之間選擇更換堆棧分配connecton實施和嘲諷的,你必須注入實際的ty你想以某種方式構建。我建議的方式是注入該類型作爲可選的模板參數。它允許您像以前一樣不加介入地使用Some::foo,但是可以在測試的情況下交換創建的連接。

template<typename ConnectionT=Connection> // models InputStream 
void Some::foo(string address, IBar& bar) 
{ 
    onBeforeConnectionCreated(); 
    { 
     ConnectionT conn(address); 
     onConnectionCreated(); 
     bar.process(conn); 
    } 
    onConnectionClosed(); 
} 

如果您知道編譯時的實際類型,我不會創建工廠和運行時多態的開銷。