Cuando quiero crear un objeto que agregue otros objetos, quiero dar acceso a los objetos internos en lugar de revelar la interfaz a los objetos internos con funciones de paso a través.
Por ejemplo, digamos que tenemos dos objetos:
class Engine;
using EnginePtr = unique_ptr<Engine>;
class Engine
{
public:
Engine( int size ) : mySize( 1 ) { setSize( size ); }
int getSize() const { return mySize; }
void setSize( const int size ) { mySize = size; }
void doStuff() const { /* do stuff */ }
private:
int mySize;
};
class ModelName;
using ModelNamePtr = unique_ptr<ModelName>;
class ModelName
{
public:
ModelName( const string& name ) : myName( name ) { setName( name ); }
string getName() const { return myName; }
void setName( const string& name ) { myName = name; }
void doSomething() const { /* do something */ }
private:
string myName;
};
Y digamos que queremos tener un objeto Car que esté compuesto por un Engine y un ModelName (obviamente, está ideado). Una posible forma de hacerlo sería dar acceso a cada uno de estos
/* give access */
class Car1
{
public:
Car1() : myModelName{ new ModelName{ "default" } }, myEngine{ new Engine{ 2 } } {}
const ModelNamePtr& getModelName() const { return myModelName; }
const EnginePtr& getEngine() const { return myEngine; }
private:
ModelNamePtr myModelName;
EnginePtr myEngine;
};
El uso de este objeto se vería así:
Car1 car1;
car1.getModelName()->setName( "Accord" );
car1.getEngine()->setSize( 2 );
car1.getEngine()->doStuff();
Otra posibilidad sería crear una función pública en el objeto Car para cada una de las funciones (deseadas) en los objetos internos, como esta:
/* passthrough functions */
class Car2
{
public:
Car2() : myModelName{ new ModelName{ "default" } }, myEngine{ new Engine{ 2 } } {}
string getModelName() const { return myModelName->getName(); }
void setModelName( const string& name ) { myModelName->setName( name ); }
void doModelnameSomething() const { myModelName->doSomething(); }
int getEngineSize() const { return myEngine->getSize(); }
void setEngineSize( const int size ) { myEngine->setSize( size ); }
void doEngineStuff() const { myEngine->doStuff(); }
private:
ModelNamePtr myModelName;
EnginePtr myEngine;
};
El segundo ejemplo se usaría así:
Car2 car2;
car2.setModelName( "Accord" );
car2.setEngineSize( 2 );
car2.doEngineStuff();
Mi preocupación con el primer ejemplo es que viola la encapsulación OO al dar acceso directo a los miembros privados.
Mi preocupación con el segundo ejemplo es que, a medida que llegamos a niveles más altos en la jerarquía de clases, podríamos terminar con clases "divinas" que tienen interfaces públicas muy grandes (viola el "I" en SÓLIDO).
¿Cuál de los dos ejemplos representa un mejor diseño OO? ¿O ambos ejemplos demuestran una falta de comprensión de OO?
fuente
No creo que necesariamente viole la encapsulación para devolver referencias al objeto envuelto, particularmente si son constantes. Ambos
std::string
ystd::vector
haz esto. Si puede comenzar a alterar las partes internas del objeto debajo de él sin pasar por su interfaz, eso es más cuestionable, pero si ya pudiera hacerlo de manera efectiva con los setters, la encapsulación era una ilusión de todos modos.Los contenedores son especialmente difíciles de encajar en este paradigma; Es difícil imaginar una lista útil que no se pueda descomponer en cabeza y cola. Hasta cierto punto, puede escribir interfaces como las
std::find()
que son ortogonales al diseño interno de la estructura de datos. Haskell va más allá con clases como Plegable y Traversible. Pero en algún momento, terminaste diciendo que todo lo que querías romper con la encapsulación ahora está dentro de la barrera de encapsulación.fuente