2011-10-18 16 views
5

En C++ a menudo utilizo objetos estilo RAII para hacer el código más confiable y asignarlos en la pila para hacer que el código sea más eficiente (y para evitar bad_alloc).Pila de objetos RAII asignados frente al principio DI

Pero la creación de un objeto de clase concreta en la pila viola el principio de inversión de dependencia (DI) y evita la burla de este objeto.

Considere el siguiente código:

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

puedo probar IBar::process, pero también quiero probar Some::foo, sin crear objeto de conexión real.

Seguramente puedo usar una fábrica, pero complicará significativamente el código e introducirá la asignación de montones.
Además, no me gusta agregar el método Connection::open, prefiero construir objetos completamente inicializados y completamente funcionales.

me gustaría hacer Connection tipo de un parámetro de plantilla para Some (o para foo si extraerlo como una función gratuita), pero no estoy seguro de que es la manera correcta (plantillas se ven como una magia negro para muchas personas, por lo que Prefiere usar polimorfismo dinámico)

+2

Las plantillas no deben ser magia negra para el programador de C++ más o menos competente, no veo ninguna razón para evitarlas.Además, no creo que la asignación de heap sea * que * costosa (esto, por supuesto, depende del software que escriba), así que tampoco veo motivos para evitarla (cuando se usa con punteros inteligentes). –

+4

@Alex B: hay una razón para evitarlos, aunque estoy de acuerdo en que no es porque sean "magia negra". Es porque si todo se inyecta a través de parámetros de plantilla, entonces todo lo que escribes es una plantilla, tu biblioteca es solo de encabezado y puede ser bastante engorrosa en términos de compilación o distribución. Aunque, supongo que con cuidado podría probar la biblioteca de encabezado único, luego compilar una TU que solo contenga las instancias que necesita la aplicación. –

+1

RAII y DI funcionan muy bien juntos, por lo que el título es engañoso, su problema es la asignación de pila vs DI. –

Respuesta

5

Lo que está haciendo ahora es "forzar el acoplamiento" de la clase RAII y la clase de proveedor de servicios (que, si desea probar, realmente debería ser una interfaz). Frente a este por:

  1. abstracción Connection en IConnection
  2. tienen una clase separada ScopedConnection que proporciona RAII encima de eso

Por ejemplo:

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

Y acepte que 'ScopedConnection' no necesita ser burlado, es" seguro "usar la versión real incluso en pruebas que se supone que aíslan' Some :: foo'. O si eso es inaceptable, aprieta los dientes e inyéctalo como un parámetro de plantilla, o usa 'scoped_ptr' para proporcionar el RAII, que como una clase estándar (o un tercero si todavía estás en C++ 03) es un disco duro aceptable dependencia. –

+0

Eso es lo que escribí sobre la fábrica. Para seguir su respuesta, debería crear una fábrica solo para Connection, o factory para muchas clases no relacionadas (como usted sugiere). Lleva esta fábrica a 'Algunos' a través de muchas capas (o haz que sea global). – Abyx

+0

@Abyx: la fábrica sería un candidato ideal para DI, lo que sería preferible a pasarlo manualmente o tener un global. Pero lo necesitas para aumentar la abstracción. – Jon

1

Por "Puedo usar una fábrica, pero complicará significativamente el código e introducirá la asignación de montones "Me refería a los siguientes pasos:

Crear clase abstracta y derivar Connection de ella

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

Agregar fábrica método para Some

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

Reemplazar connecton pila asignado por puntero inteligente

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

que elegir entre su real implementación y la burlada, tiene que inyectar el ty real pe que quiere construir de alguna manera. La forma en que lo recomendaría es inyectar el tipo como un parámetro de plantilla opcional. Le permite usar discretamente Some::foo como solía hacerlo, pero le permite intercambiar la conexión creada en caso de una prueba.

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

no crearía la sobrecarga de un polimorfismo de fábrica y tiempo de ejecución si se conoce el tipo real en tiempo de compilación.

Cuestiones relacionadas