Ketika saya ingin membuat objek yang menggabungkan objek lain, saya mendapati diri saya ingin memberikan akses ke objek internal alih-alih mengungkapkan antarmuka ke objek internal dengan fungsi passthrough.
Misalnya, kita memiliki dua objek:
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;
};
Dan katakanlah kita ingin memiliki objek Mobil yang terdiri dari Engine dan ModelName (ini jelas dibuat-buat). Salah satu cara yang mungkin untuk melakukannya adalah memberikan akses ke masing-masing
/* 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;
};
Menggunakan objek ini akan terlihat seperti ini:
Car1 car1;
car1.getModelName()->setName( "Accord" );
car1.getEngine()->setSize( 2 );
car1.getEngine()->doStuff();
Kemungkinan lain adalah membuat fungsi publik pada objek Mobil untuk setiap fungsi (yang diinginkan) pada objek internal, seperti ini:
/* 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;
};
Contoh kedua akan digunakan seperti ini:
Car2 car2;
car2.setModelName( "Accord" );
car2.setEngineSize( 2 );
car2.doEngineStuff();
Kekhawatiran saya dengan contoh pertama adalah bahwa hal itu melanggar enkapsulasi OO dengan memberikan akses langsung ke anggota pribadi.
Kekhawatiran saya dengan contoh kedua adalah bahwa, ketika kita mencapai level yang lebih tinggi dalam hirarki kelas, kita dapat berakhir dengan kelas "seperti dewa" yang memiliki antarmuka publik yang sangat besar (melanggar "I" dalam SOLID).
Manakah dari dua contoh yang mewakili desain OO yang lebih baik? Atau apakah kedua contoh menunjukkan kurangnya pemahaman OO?
sumber
Saya tidak berpikir itu pasti melanggar enkapsulasi untuk mengembalikan referensi ke objek yang dibungkus, terutama jika mereka const. Keduanya
std::string
danstd::vector
lakukan ini. Jika Anda dapat mulai mengubah internal objek dari bawah tanpa melalui antarmuka, itu lebih dipertanyakan, tetapi jika Anda sudah bisa melakukannya secara efektif dengan setter, enkapsulasi adalah ilusi pula.Wadah sangat sulit untuk masuk ke dalam paradigma ini; sulit membayangkan daftar berguna yang tidak dapat diuraikan menjadi kepala dan ekor. Sampai batas tertentu, Anda dapat menulis antarmuka seperti
std::find()
yang ortogonal dengan tata letak internal struktur data. Haskell melangkah lebih jauh dengan kelas-kelas seperti Foldable dan Traversible. Tetapi pada titik tertentu, Anda akhirnya mengatakan bahwa semua yang Anda ingin hentikan untuk melakukan enkapsulasi sekarang berada di dalam penghalang enkapsulasi.sumber