Pengecoran
Ini hampir pasti akan menjadi garis singgung lengkap dengan pendekatan buku yang dikutip, tetapi satu cara untuk menyesuaikan diri dengan ISP adalah dengan merangkul pola pikir casting di satu area pusat basis kode Anda menggunakan QueryInterface
pendekatan gaya-COM.
Banyak godaan untuk mendesain antarmuka yang tumpang tindih dalam konteks antarmuka murni seringkali berasal dari keinginan untuk membuat antarmuka "mandiri" lebih dari melakukan satu tanggung jawab yang tepat, seperti penembak jitu.
Misalnya, mungkin tampak aneh untuk mendesain fungsi klien seperti ini:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `position` and `parenting` parameters should point to the
// same object.
Vec2i abs_position(IPosition* position, IParenting* parenting)
{
const Vec2i xy = position->xy();
auto parent = parenting->parent();
if (parent)
{
// If the entity has a parent, return the sum of the
// parent position and the entity's local position.
return xy + abs_position(dynamic_cast<IPosition*>(parent),
dynamic_cast<IParenting*>(parent));
}
return xy;
}
... juga sangat jelek / berbahaya, mengingat bahwa kita membocorkan tanggung jawab untuk melakukan pengecoran rawan kesalahan ke kode klien menggunakan antarmuka ini dan / atau melewatkan objek yang sama sebagai argumen beberapa kali ke beberapa parameter yang sama fungsi. Jadi kita akhirnya sering ingin merancang antarmuka yang lebih terdilusi yang mengkonsolidasikan keprihatinan IParenting
dan IPosition
di satu tempat, seperti IGuiElement
atau sesuatu seperti itu yang kemudian menjadi rentan terhadap tumpang tindih dengan masalah antarmuka ortogonal yang juga akan tergoda untuk memiliki lebih banyak fungsi anggota untuk alasan "swasembada" yang sama.
Mencampur Tanggung Jawab vs. Casting
Ketika merancang antarmuka dengan tanggung jawab ultra-singular yang benar-benar disuling, godaan sering kali adalah menerima beberapa antarmuka yang downcasting atau menggabungkan untuk memenuhi beberapa tanggung jawab (dan karenanya menginjak baik ISP maupun SRP).
Dengan menggunakan pendekatan gaya-COM (hanya QueryInterface
bagian), kami bermain dengan pendekatan downcasting tetapi mengkonsolidasikan casting ke satu tempat sentral dalam basis kode, dan dapat melakukan sesuatu yang lebih seperti ini:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should implement `IPosition` and optionally `IParenting`.
Vec2i abs_position(Object* obj)
{
// `Object::query_interface` returns nullptr if the interface is
// not provided by the entity. `Object` is an abstract base class
// inherited by all entities using this interface query system.
IPosition* position = obj->query_interface<IPosition>();
assert(position && "obj does not implement IPosition!");
const Vec2i xy = position->xy();
IParenting* parenting = obj->query_interface<IParenting>();
if (parenting && parenting->parent()->query_interface<IPosition>())
{
// If the entity implements IParenting and has a parent,
// return the sum of the parent position and the entity's
// local position.
return xy + abs_position(parenting->parent());
}
return xy;
}
... tentu saja mudah-mudahan dengan pembungkus tipe-aman dan semua yang Anda dapat membangun secara terpusat untuk mendapatkan sesuatu yang lebih aman daripada pointer mentah.
Dengan ini, godaan untuk mendesain antarmuka yang tumpang tindih sering dikurangi seminimal mungkin. Hal ini memungkinkan Anda untuk mendesain antarmuka dengan tanggung jawab yang sangat tunggal (kadang-kadang hanya satu fungsi anggota di dalamnya) yang Anda dapat mencampur dan mencocokkan semua yang Anda suka tanpa khawatir tentang ISP, dan mendapatkan fleksibilitas mengetik pseudo-bebek saat runtime di C ++ (meskipun tentu saja dengan trade-off dari penalti runtime ke objek permintaan untuk melihat apakah mereka mendukung antarmuka tertentu). Bagian runtime dapat menjadi penting dalam, katakanlah, pengaturan dengan kit pengembangan perangkat lunak di mana fungsi tidak akan memiliki informasi waktu kompilasi dari plugin sebelumnya yang mengimplementasikan antarmuka ini.
Templat
Jika templat adalah suatu kemungkinan (kami memiliki info waktu kompilasi yang diperlukan di muka yang tidak hilang pada saat kami mendapatkan suatu objek, yaitu), maka kami dapat melakukannya:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should have `position` and `parent` methods.
template <class Entity>
Vec2i abs_position(Entity& obj)
{
const Vec2i xy = obj.xy();
if (obj.parent())
{
// If the entity has a parent, return the sum of the parent
// position and the entity's local position.
return xy + abs_position(obj.parent());
}
return xy;
}
... tentu saja dalam kasus seperti itu, parent
metode harus mengembalikan Entity
tipe yang sama , dalam hal ini kita mungkin ingin menghindari antarmuka langsung (karena mereka akan sering ingin kehilangan informasi jenis yang mendukung bekerja dengan basis pointer).
Sistem Entitas-Komponen
Jika Anda mulai mengejar pendekatan gaya COM lebih jauh dari sudut pandang fleksibilitas atau kinerja, Anda akan sering berakhir dengan sistem komponen entitas yang mirip dengan apa yang diterapkan mesin game di industri. Pada titik itu Anda akan benar-benar tegak lurus terhadap banyak pendekatan berorientasi objek, tetapi ECS mungkin berlaku untuk desain GUI (satu tempat yang saya renungkan menggunakan ECS di luar fokus berorientasi adegan, tetapi menganggapnya terlambat setelah menetapkan pendekatan gaya COM untuk mencoba di sana).
Perhatikan bahwa solusi gaya-COM ini benar-benar ada di luar sana sejauh desain alat GUI berjalan, dan ECS akan lebih, jadi itu bukan sesuatu yang akan didukung oleh banyak sumber daya. Namun itu pasti akan memungkinkan Anda untuk mengurangi godaan untuk merancang antarmuka yang memiliki tanggung jawab yang tumpang tindih ke minimum absolut, sering menjadikannya tidak menjadi perhatian.
Pendekatan Pragmatis
Alternatifnya, tentu saja, sedikit rileks penjaga Anda, atau desain antarmuka di tingkat granular dan kemudian mulai mewarisi mereka untuk membuat antarmuka kasar yang Anda gunakan, seperti IPositionPlusParenting
yang berasal dari keduanya IPosition
danIParenting
(semoga dengan nama yang lebih baik dari itu). Dengan antarmuka murni, seharusnya tidak melanggar ISP sebanyak pendekatan monolitik mendalam-hierarki yang biasa diterapkan (Qt, MFC, dll. Di mana dokumentasi sering merasa perlu untuk menyembunyikan anggota yang tidak relevan mengingat tingkat pelanggaran ISP yang berlebihan dengan jenis-jenis itu). desain), sehingga pendekatan pragmatis mungkin hanya menerima beberapa tumpang tindih di sana-sini. Namun pendekatan gaya-COM semacam ini menghindari perlunya membuat antarmuka terkonsolidasi untuk setiap kombinasi yang pernah Anda gunakan. Perhatian "swasembada" sepenuhnya dihilangkan dalam kasus-kasus seperti itu, dan itu akan sering menghilangkan sumber utama godaan untuk merancang antarmuka yang memiliki tanggung jawab yang tumpang tindih yang ingin bertarung dengan SRP dan ISP.