Wenn ich ein Objekt erstellen möchte, das andere Objekte aggregiert, möchte ich Zugriff auf die internen Objekte gewähren, anstatt die Schnittstelle zu den internen Objekten mit Passthrough-Funktionen anzuzeigen.
Angenommen, wir haben zwei Objekte:
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;
};
Nehmen wir an, wir möchten ein Autoobjekt haben, das sowohl aus einer Engine als auch aus einem ModelName besteht (dies ist offensichtlich erfunden). Ein möglicher Weg, dies zu tun, wäre, jedem von ihnen Zugang zu gewähren
/* 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;
};
Die Verwendung dieses Objekts würde folgendermaßen aussehen:
Car1 car1;
car1.getModelName()->setName( "Accord" );
car1.getEngine()->setSize( 2 );
car1.getEngine()->doStuff();
Eine andere Möglichkeit wäre, eine öffentliche Funktion für das Autoobjekt für jede der (gewünschten) Funktionen für die internen Objekte wie folgt zu erstellen:
/* 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;
};
Das zweite Beispiel würde folgendermaßen verwendet:
Car2 car2;
car2.setModelName( "Accord" );
car2.setEngineSize( 2 );
car2.doEngineStuff();
Mein Anliegen beim ersten Beispiel ist, dass es die OO-Kapselung verletzt, indem es den privaten Mitgliedern direkten Zugriff gewährt.
Meine Sorge mit dem zweiten Beispiel ist, dass wir, wenn wir zu höheren Ebenen in der Klassenhierarchie gelangen, mit "gottähnlichen" Klassen enden könnten, die sehr große öffentliche Schnittstellen haben (was das "Ich" in SOLID verletzt).
Welches der beiden Beispiele steht für ein besseres OO-Design? Oder zeigen beide Beispiele einen Mangel an OO-Verständnis?
quelle
Ich denke nicht, dass es notwendigerweise die Kapselung verletzt, Verweise auf das umschlossene Objekt zurückzugeben, insbesondere wenn sie const sind. Beides
std::string
undstd::vector
mach das. Wenn Sie anfangen können, die Interna des Objekts darunter zu ändern, ohne die Schnittstelle zu durchlaufen, ist dies fragwürdiger. Wenn Sie dies jedoch bereits mit Setzern effektiv tun könnten, wäre die Kapselung ohnehin eine Illusion.Container sind besonders schwer in dieses Paradigma zu integrieren. Es ist schwer vorstellbar, dass eine nützliche Liste nicht in Kopf und Schwanz zerlegt werden kann. Bis zu einem gewissen Grad können Sie Schnittstellen schreiben
std::find()
, die orthogonal zum internen Layout der Datenstruktur sind. Haskell geht noch weiter mit Klassen wie Foldable und Traversible. Aber irgendwann haben Sie gesagt, dass sich alles, wofür Sie die Kapselung unterbrechen wollten, jetzt innerhalb der Kapselungsbarriere befindet.quelle