Panggil metode kelas turunan dari loop daftar penunjuk dasar (OOD)

Masalah

Saya mengalami masalah sederhana, meskipun saya tidak dapat menemukan OOD yang tepat untuk masalah tersebut.

Apa yang saya punya:

  • Kelas dasar
  • Subkelas menambahkan metode baru foo()
  • Daftar pointer ke instance kelas dasar

Yang saya perlukan:

Saya perlu mengulang daftar ini dan memanggil foo() untuk objek yang mendukung metode ini, yaitu objek dari (atau berasal dari) subkelas yang disebutkan di atas. Atau secara umum, saya memerlukan akses polimorfik "tidak berbau" ke subkelas melalui daftar pointer ke kelas dasar.

Contoh Kode

class Entity {
    // ...
    // This class contains methods also needed by subclasses.
};

class SaveableEntity : public Entity {
public:
    virtual void save() = 0;
};

// SaveableEntity has multiple subclasses with specific save() implementations.

std::vector<Entity *> list;
for (Entity *entity : list) {
    // Here I need to save() the descendants of a SaveableEntity type.
}

Saya mempunyai beberapa ide, namun tidak ada satupun yang menurut saya tepat. Berikut beberapa di antaranya:

Metode 1: dynamic_cast

Karena beberapa elemen dapat disimpan dan ada pula yang tidak, cara paling jelas yang saya lihat adalah transmisi dinamis:

std::vector<Entity *> list;
for (Entity *entity : list) {
    auto saveable = dynamic_cast<SaveableEntity *>(entity);
    if (saveable) {
        saveable->save();
    }
}

Namun, menggunakan dynamic_cast sepertinya OOD yang buruk dalam situasi ini (koreksi saya jika saya salah). Selain itu, pendekatan ini dapat dengan mudah menyebabkan pelanggaran terhadap LSP.

Metode 2: Pindahkan save() ke kelas dasar

Saya dapat menghapus SaveableEntity dan memindahkan metode save() ke basis Entity. Namun, hal ini membuat kami menerapkan metode dummy:

class Entity {
    virtual void save() {
        // Do nothing, override in subclasses
    }
};

Hal ini menghilangkan penggunaan dynamic_cast, namun metode dummy tampaknya masih belum benar: sekarang kelas dasar menyimpan informasi (metode save()) yang sama sekali tidak berhubungan dengannya.

Metode 3: Terapkan pola desain

  • Pola Strategi: kelas SaveStrategy dan subkelasnya seperti NoSaveStrategy, SomeSaveStrategy, SomeOtherSaveStrategy, dll. Sekali lagi, kehadiran NoSaveStrategy membawa kita kembali ke kelemahan metode sebelumnya: kelas dasar harus mengetahui detail khusus tentang subkelasnya, yang sepertinya desainnya buruk.
  • Pola Proxy atau Dekorator dapat dengan mudah merangkum dynamic_cast, namun ini hanya akan menyembunyikan kode yang tidak diinginkan, bukan menghilangkan desain buruk itu sendiri.
  • Tambahkan beberapa lapisan komposisi-over-warisan, dan seterusnya dan seterusnya...

Pertanyaan

Mungkin saya melewatkan beberapa solusi yang jelas, atau mungkin metode yang dijelaskan (1 atau 2) tidak seburuk dan berbau dalam konteks khusus ini seperti yang saya lihat.

Jadi pendekatan desain apa yang cocok dalam situasi seperti itu?


person kefir500    schedule 22.11.2018    source sumber
comment
Anda masih dapat mengimplementasikan fungsi virtual murni. ini akan membuatnya menjadi perilaku default. tapi aku tidak yakin dengan baunya :-/   -  person MauriceRandomNumber    schedule 22.11.2018
comment
Lihatlah masalah Anda dari sudut ini: antarmuka kelas dasar tidak mendukung foo. Dan Anda ingin melakukan melalui antarmuka tersebut (mengakses objek nyata melalui pointer kelas dasar) sesuatu ( foo ) yang tidak didukungnya.   -  person Andrew Kashpur    schedule 22.11.2018


Jawaban (2)


Ada solusi #4 yang didorong oleh pemrograman berorientasi data (sudah ada pembahasan bagus mengenai hal ini di cppcon 2018, tersedia di youtube): memiliki dua daftar. Satu daftar untuk semua SavableEntitys dan yang lainnya untuk Entitys yang tidak dapat disimpan.

Sekarang, Anda mengulangi daftar pertama dan ->save() item tersebut.

Keuntungan utamanya adalah Anda hanya melakukan iterasi pada entitas yang relevan. Dengan beberapa pemfaktoran ulang (mungkin besar), Anda dapat memiliki kumpulan objek daripada petunjuk ke beberapa objek. Hal ini akan meningkatkan lokalitas data dan secara drastis mengurangi jumlah cache yang hilang.

person YSC    schedule 22.11.2018

Ide pertama saya adalah menambah kelas dasar dengan metode virtual bool trySave(). Standarnya bisa return false;, sedangkan SaveableEntity memberikan penggantian ini:

class SaveableEntity : public Entity {
public:
    virtual bool trySave() final override
    {
        save();
        return true;
    }

    // You could also make this protected.
    virtual void save() = 0;
};

Apakah ini lebih tepat daripada saran @ YSC untuk kasus khusus Anda adalah sesuatu yang harus Anda putuskan sendiri. Ini mirip dengan memindahkan save() ke kelas dasar tetapi tidak terlalu membingungkan pengguna.

person Max Langhof    schedule 22.11.2018
comment
Terima kasih atas jawabannya, Namun, meskipun ini meningkatkan metode #2, metode ini tetap tidak menyelesaikan kelas dasar yang maha tahu yang (idealnya) tidak mengetahui apa pun tentang kelas turunan. - person kefir500; 26.11.2018