Quando eu quero criar um objeto que agregue outros objetos, me desejo dar acesso aos objetos internos em vez de revelar a interface para os objetos internos com funções de passagem.
Por exemplo, digamos que temos dois 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;
};
Digamos que queremos ter um objeto Car que seja composto de um Engine e um ModelName (isso é obviamente inventado). Uma maneira possível de fazer isso seria dar acesso a cada uma dessas
/* 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;
};
O uso desse objeto ficaria assim:
Car1 car1;
car1.getModelName()->setName( "Accord" );
car1.getEngine()->setSize( 2 );
car1.getEngine()->doStuff();
Outra possibilidade seria criar uma função pública no objeto Car para cada uma das funções (desejadas) nos objetos internos, assim:
/* 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;
};
O segundo exemplo seria usado assim:
Car2 car2;
car2.setModelName( "Accord" );
car2.setEngineSize( 2 );
car2.doEngineStuff();
Minha preocupação com o primeiro exemplo é que ele viola o encapsulamento de OO, fornecendo acesso direto aos membros privados.
Minha preocupação com o segundo exemplo é que, à medida que atingimos níveis mais altos na hierarquia de classes, poderíamos terminar com classes "divinas" que possuem interfaces públicas muito grandes (viola o "I" no SOLID).
Qual dos dois exemplos representa melhor design de OO? Ou os dois exemplos demonstram falta de compreensão de OO?
fonte
Não acho que viole necessariamente o encapsulamento para retornar referências ao objeto empacotado, principalmente se forem const. Ambos
std::string
estd::vector
faça isso. Se você pode começar a alterar as partes internas do objeto por baixo dele sem passar por sua interface, isso é mais questionável, mas se você já conseguia fazer isso com setters, o encapsulamento era uma ilusão de qualquer maneira.Os contêineres são especialmente difíceis de se encaixar nesse paradigma; é difícil imaginar uma lista útil que não possa ser decomposta em cabeça e cauda. Até certo ponto, você pode escrever interfaces como
std::find()
estas ortogonais ao layout interno da estrutura de dados. Haskell vai além com classes como Dobrável e Traversível. Mas em algum momento, você acabou dizendo que tudo o que queria quebrar o encapsulamento agora está dentro da barreira do encapsulamento.fonte