2011-10-18 14 views
5

W C++ często używam obiektów w stylu RAII, aby uczynić kod bardziej niezawodnym i przydzielać je na stos, aby kod był bardziej wydajny (i aby uniknąć bad_alloc).Stack przydzielonych obiektów RAII vs zasada DI

Ale tworzenie obiektu konkretnej klasy na stosie narusza zasadę inwersji zależności (DI) i zapobiega przedśmiekaniu tego obiektu.

Rozważmy następujący kod:

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(); 
} 

mogę przetestować IBar::process, ale także chcą przetestować Some::foo, bez tworzenia prawdziwego obiektu Connection.

Z pewnością mogę użyć fabryki, ale znacznie skomplikuje to kod i wprowadzi alokację sterty.
Ponadto, nie lubię dodawać metody Connection::open, wolę konstruować całkowicie zainicjalizowane i w pełni funkcjonalne obiekty.

chciałbym zrobić Connection typ parametrem szablonu dla Some (lub foo jeśli wyodrębnić je jako wolnej funkcji), ale nie jestem pewien, że jest to właściwa droga (szablony wyglądają jak czarna magia dla wielu ludzi, więc ja preferuje użycie dynamicznego polimorfizmu)

+2

Szablony nie powinny być czarną magią dla mniej lub bardziej kompetentnego programisty C++, nie widzę powodu, aby ich unikać.Nie uważam też, że alokacja sterty jest * taka * droga (to oczywiście zależy od oprogramowania, które piszesz), więc nie widzę powodu, aby tego unikać (w przypadku użycia inteligentnych wskaźników). –

+4

@Alex B: jest pewien powód, aby ich unikać, chociaż zgadzam się, że to nie dlatego, że są "czarną magią". Dzieje się tak, ponieważ jeśli wszystko zostanie wstrzyknięte za pomocą parametrów szablonu, wszystko, co napiszesz, jest szablonem, twoja biblioteka jest tylko nagłówkowa i może stać się dość kłopotliwa pod względem kompilacji lub dystrybucji. Chociaż, przypuszczam, że z ostrożnością można by przetestować bibliotekę tylko nagłówkową, następnie zbuduj z niej jednostkę, która zawiera tylko instancje wymagane przez aplikację. –

+1

RAII i DI świetnie ze sobą współpracują, więc tytuł jest mylący, Twoim problemem jest alokacja stosów kontra DI. –

Odpowiedz

5

To, co teraz robisz, to "przymusowe łączenie" klasy RAII i klasy dostawcy usług (która, jeśli chcesz testowalności, powinna w rzeczywistości być interfejsem). Ten adres przez:

  1. abstrahując Connection do IConnection
  2. mieć osobne ScopedConnection Klasa udostępniająca RAII na szczycie tej

Na przykład:

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

Przyjmijmy, że 'ScopedConnection' nie musi być wyszydzany," bezpieczne "jest używanie prawdziwej wersji nawet w testach, które mają izolować' Some :: foo'. Lub jeśli jest to niedopuszczalne, wyszczotkuj zęby i wprowadź je jako parametr szablonu lub użyj 'scoped_ptr', aby dostarczyć RAII, który jako standardowa klasa (lub trzecia strona, jeśli nadal jesteś w C++ 03) jest akceptowalnym ciężkim zależność. –

+0

Tak napisałem o fabryce. Aby odpowiedzieć na twoją odpowiedź, powinienem stworzyć fabrykę tylko dla połączenia lub fabryki dla wielu niepowiązanych klas (tak jak sugerujesz). Przenieś tę fabrykę do 'Some' przez wiele warstw (lub uczyń ją globalną). – Abyx

+0

@Abyx: Fabryka byłaby głównym kandydatem dla DI, co byłoby lepszym rozwiązaniem niż przekazanie go ręcznie lub posiadanie globalnego. Ale potrzebujesz go do zwiększenia abstrakcji. – Jon

1

Przez „można używać fabryka, ale znacznie skomplikuje kod i wprowadzi przydzielanie sterty "Miałem na myśli następujące kroki:

Tworzenie klasy abstrakcyjnej i czerpać Connection z niego

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

dodawania fabryka metoda Some

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

Wymień stosu przyznane connecton przez inteligentnego wskaźnika

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

wybierać pomiędzy rzeczywisty implementacja i wyśmiewana, trzeba wstrzyknąć rzeczywistą ty które chcesz zbudować w pewien sposób. Tak, jak zaleciłbym, to wstrzykiwanie typu jako opcjonalnego parametru szablonu. Pozwala na dyskretne korzystanie z funkcji Some::foo, ale umożliwia zamianę utworzonego połączenia w przypadku testu.

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

Nie tworzyłbym narzutów polimorfizmu fabryki i środowiska wykonawczego, jeśli znany jest aktualny typ podczas kompilacji.