Ada beberapa pertanyaan serupa di luar sana 1 ,2 ,3 ,4 , tetapi tampaknya bukan masalah sebenarnya dalam pertanyaan ini, juga solusinya tidak tampak optimal.
Ini adalah pertanyaan OOP umum, dengan asumsi polimorfisme, generik, dan mixin tersedia. Bahasa aktual yang digunakan adalah OOP Javascript (Typescript), tetapi masalah yang sama di Jawa atau C ++.
Saya memiliki hierarki kelas paralel, yang terkadang berbagi perilaku yang sama (antarmuka dan implementasi), tetapi kadang-kadang masing-masing memiliki perilaku 'dilindungi' sendiri. Diilustrasikan seperti ini:
Ini hanya untuk tujuan ilustrasi ; itu bukan diagram kelas yang sebenarnya. Untuk membacanya:
- Apa pun di hierarki umum (tengah) dibagi antara hierarki Kanvas (kiri) dan SVG (kanan). Maksud saya adalah antarmuka dan implementasi.
- Apa pun hanya di kolom kiri atau kanan berarti perilaku (metode dan anggota) khusus untuk hierarki itu. Sebagai contoh:
- Baik hierarki kiri dan kanan menggunakan mekanisme validasi yang persis sama, ditampilkan sebagai metode tunggal (
Viewee.validate()
) pada hierarki umum. - Hanya hierarki kanvas yang memiliki metode
paint()
. Metode ini memanggil metode cat pada semua anak. - Hirarki SVG perlu mengganti
addChild()
metodeComposite
, tetapi tidak demikian halnya dengan hierarki kanvas.
- Baik hierarki kiri dan kanan menggunakan mekanisme validasi yang persis sama, ditampilkan sebagai metode tunggal (
- Konstruksi dari hierarki dua sisi tidak dapat dicampur. Sebuah pabrik memastikan hal itu.
Solusi I - Menggoda Warisan Terpisah
Fowler's Tease Apart Inheritance tampaknya tidak melakukan pekerjaan di sini, karena ada beberapa perbedaan antara kedua paralel.
Solusi II - Mixins
Ini adalah satu-satunya yang saat ini dapat saya pikirkan. Dua hierarki dikembangkan secara terpisah, tetapi pada setiap tingkat kelas-kelas tersebut bercampur dalam kelas umum, yang bukan bagian dari hierarki kelas. Menghilangkan structural
garpu, akan terlihat seperti ini:
Perhatikan bahwa setiap kolom akan berada dalam namespace sendiri, sehingga nama kelas tidak akan konflik.
Pertanyaan
Adakah yang bisa melihat kesalahan dengan pendekatan ini? Adakah yang bisa memikirkan solusi yang lebih baik?
Tambahan
Berikut ini beberapa contoh kode cara menggunakannya. Namespace svg
dapat diganti dengan canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Pada dasarnya, dalam run-time klien menyusun instance hirarki subclass Viewee; seperti itu:
Katakanlah semua orang yang dilihat ini berasal dari hierarki kanvas, mereka diberikan dengan melintasi hierarki yang dapat memanggil paint()
setiap orang yang dilihat. Jika mereka berasal dari hierarki svg, pengunjung yang tahu cara menambahkan diri ke DOM, tetapi tidak ada paint()
traversal.
Jawaban:
Pendekatan kedua memisahkan antarmuka dengan lebih baik, mengikuti prinsip pemisahan antarmuka.
Namun, saya akan menambahkan antarmuka Paintable.
Saya juga akan mengganti beberapa nama. Tidak perlu membuat kebingungan:
sumber
Itu sebenarnya tidak benar sama sekali. Script memiliki keunggulan substansial dibandingkan Java- yaitu, pengetikan struktural. Anda dapat melakukan sesuatu yang serupa di C ++ dengan templat yang diketik bebek, tetapi ini jauh lebih sulit.
Pada dasarnya, tentukan kelas Anda, tetapi jangan repot-repot memperluas atau mendefinisikan antarmuka apa pun. Kemudian cukup mendefinisikan antarmuka yang Anda butuhkan dan menganggapnya sebagai parameter. Kemudian, objek bisa cocok dengan antarmuka itu - mereka tidak perlu tahu untuk memperpanjangnya terlebih dahulu. Setiap fungsi dapat mendeklarasikan dengan tepat dan hanya bit-bit yang mereka berikan, dan kompiler akan memberikan Anda sebuah pass jika tipe terakhir yang memenuhi itu, meskipun kelas-kelas tidak benar-benar memperluas antarmuka tersebut secara eksplisit.
Ini membebaskan Anda dari kebutuhan untuk benar-benar mendefinisikan hierarki antarmuka dan menentukan kelas mana yang harus memperluas antarmuka mana.
Cukup tentukan setiap kelas dan lupakan tentang pengetikan antar muka-struktural akan menanganinya.
Sebagai contoh:
Ini adalah naskah yang benar-benar sah. Perhatikan bahwa fungsi yang dikonsumsi tidak tahu atau tidak peduli sama sekali tentang kelas dasar atau antarmuka yang digunakan dalam definisi kelas.
Tidak masalah jika kelas terkait atau tidak berdasarkan warisan. Tidak masalah jika mereka memperluas antarmuka Anda. Cukup tentukan antarmuka sebagai parameter, dan Anda selesai - semua kelas yang memenuhi itu diterima.
sumber
validate()
metode (implementasi yang identik untuk keduanya); maka hanya svg.Viewee perlu mengganti addChild () , sementara hanya canvas.Viewee perlu mengecat () (yang memanggil paint () pada semua anak - yang merupakan anggota kelas dasar Composite ). Jadi saya tidak bisa membayangkan ini dengan pengetikan struktural.SVGViewee.addChild()
, tetapiCanvasViewee
juga perlu fitur yang persis sama. Jadi sepertinya masuk akal bagi saya bahwa keduanya melekat dari Composite?Ikhtisar Cepat
Solusi 3: Pola Desain Perangkat Lunak "Parallel Class Hierarchy" adalah teman Anda.
Jawaban Panjang
Desain Anda MULAI BENAR. Ini dapat dioptimalkan, beberapa kelas atau anggota dapat dihapus, tetapi, ide "hierarki paralel" yang Anda terapkan untuk menyelesaikan masalah IS BENAR.
Sudah beberapa kali berurusan dengan konsep yang sama, biasanya dalam hierarki kontrol.
Setelah beberapa saat, saya BERAKHIRNYA MELAKUKAN SOLUSI SAMA DARI PEMBANGUN LAINNYA, yang kadang-kadang disebut Pola Desain "Paralel Hierarki" atau Pola Desain "Dual Hierarki".
(1) Apakah Anda pernah membagi satu kelas menjadi satu hirarki kelas?
(2) Apakah Anda pernah membagi satu kelas menjadi beberapa kelas, tanpa hierarki?
Jika Anda telah menerapkan solusi sebelumnya, secara terpisah, maka itu adalah cara untuk menyelesaikan beberapa masalah.
Tapi, bagaimana jika kita menggabungkan dua solusi ini secara bersamaan?
Gabungkan mereka, dan, Anda akan mendapatkan "Pola Desain" ini.
Penerapan
Sekarang, mari kita terapkan Pola Desain Perangkat Lunak "Parallel Class Hierarchy", untuk kasus Anda.
Saat ini Anda memiliki 2 atau lebih hierarki kelas independen, yang sangat mirip, memiliki asosiasi atau tujuan yang sama, memiliki properti atau metode yang serupa.
Anda ingin menghindari duplikasi kode atau anggota ("konsistensi"), namun, Anda tidak dapat menggabungkan kelas ini secara langsung menjadi satu, karena perbedaan di antara mereka.
Jadi, hierarki Anda sangat mirip dengan angka ini, namun, ada lebih dari satu:
Dalam hal ini, belum disertifikasi, Pola Desain, BEBERAPA HIERARCHI SIMILAR, BERGABUNG, DALAM SEBUAH TUNGGAL TUNGGAL, dan masing-masing kelas bersama atau umum diperluas dengan subklasifikasi.
Perhatikan, bahwa solusi ini rumit, karena Anda sudah berurusan dengan beberapa hierarki, oleh karena itu skenario yang kompleks.
1 Kelas Root
Di setiap hierarki ada kelas "root" bersama.
Dalam kasus Anda, ada kelas "Komposit" yang independen, untuk setiap hierarki, yang dapat memiliki beberapa properti yang serupa, dan beberapa metode yang serupa.
Beberapa anggota tersebut dapat digabung, beberapa anggota tersebut tidak dapat digabung.
Jadi, apa yang bisa dilakukan pengembang, adalah membuat kelas root base, dan mensubklasifikasikan kasus yang setara untuk setiap hierarki.
Pada Gambar 2, Anda dapat melihat diagram hanya untuk kelas ini, di mana setiap kelas, menyimpannya namespace.
Anggota, dihilangkan, sekarang.
Seperti yang Anda perhatikan, setiap kelas "Komposit" tidak lagi berada dalam hierarki yang terpisah, tetapi digabung menjadi satu hierarki bersama atau umum.
Kemudian, mari kita tambahkan anggota, mereka yang sama, dapat dipindahkan ke superclass, dan mereka yang berbeda, untuk setiap kelas dasar.
Dan seperti yang sudah Anda ketahui, metode "virtual" atau "kelebihan beban" didefinisikan dalam kelas dasar, tetapi diganti dalam subkelas. Seperti Gambar 3.
Perhatikan bahwa mungkin ada beberapa kelas tanpa anggota, dan, Anda mungkin tergoda untuk menghapus kelas-kelas itu, DONT. Mereka disebut "Kelas Hollow", "Kelas Enumeratif", dan nama lainnya.
2 Subclass
Mari kita kembali ke diagram pertama. Setiap kelas "Composite", memiliki subclass "Viewee", di setiap hierarki.
Proses ini diulang untuk setiap kelas. Catatan dari Gambar 4, kelas "Common :: Viewee" turun dari "Common :: Composite", tetapi, untuk kesederhanaan, kelas "Common :: Composite" dihilangkan, dari diagram.
Anda akan mencatat, bahwa "Canvas :: Viewee" dan "SVG :: Viewee", TIDAK LEBIH LAMA dari masing-masing "Composite", tetapi, dari "Common :: Viewee" yang umum.
Anda dapat menambahkan anggota, sekarang.
3 Ulangi Prosesnya
Proses akan berlanjut, untuk setiap kelas, "Canvas :: Visual" tidak akan turun dari "Canvas :: Viewee", buit dari "Commons :: Visual", "Canvas :: Structural" tidak akan turun dari "Canvas :: Viewee ", buit dari" Commons :: Structural ", dan sebagainya.
4 Diagram Hirarki 3D
Anda akan selesai mendapatkan semacam diagram 3D, dengan beberapa lapisan, lapisan atas, memiliki hierarki "Umum" dan lapisan bawah, memiliki setiap hierarki tambahan.
Hirarki kelas independen asli Anda, tempat sesuatu yang mirip dengan ini (Gambar 6):
Perhatikan bahwa beberapa kelas dihilangkan, dan seluruh hierarki "Kanvas" dihilangkan, untuk secara sederhana.
Hirarki kelas terintegrasi akhir mungkin sesuatu yang mirip dengan ini:
Perhatikan bahwa beberapa kelas dihilangkan, dan seluruh kelas "Kanvas" dihilangkan, untuk secara sederhana, tetapi, akan mirip dengan kelas "SVG".
Kelas "Biasa" dapat direpresentasikan sebagai satu lapisan diagram 3D, kelas "SVG" di lapisan lain, dan kelas "Kanvas", di lapisan ketiga.
Periksa, bahwa setiap lapisan terkait dengan yang pertama, di mana, setiap kelas memiliki kelas induk dari hierarki "Umum".
Implementasi kode mungkin perlu menggunakan salah satu, warisan antarmuka, warisan kelas, atau "mixins", tergantung pada apa yang didukung oleh Bahasa Pemrograman Anda.
Ringkasan
Seperti solusi pemrograman apa pun, jangan terburu-buru ke optimasi, optimasi sangat penting, namun, optimasi yang buruk, dapat menjadi masalah yang lebih besar, daripada masalah aslinya.
Saya tidak merekomendasikan menerapkan "Solusi 1" atau "Solusi 2".
Dalam "Solusi 1" tidak berlaku, karena, warisan, diperlukan dalam setiap kasus.
"Solusi 2", "Mixins" dapat diterapkan, tetapi, setelah mendesain kelas dan hierarki.
Mixins, adalah alternatif untuk pewarisan berbasis antarmuka, atau beberapa pewarisan berbasis kelas.
Usulan saya, Solusi 3, kadang-kadang disebut Pola Desain "Parallel Hierarchy" atau Pola Desain "Dual Hierarchy".
Banyak pengembang / desainer tidak akan setuju dengan itu, dan percaya itu seharusnya tidak ada. Tapi, saya telah menggunakan oleh diri sendiri, dan pengembang lain, sebagai solusi umum untuk masalah, seperti salah satu pertanyaan Anda.
Satu hal lagi yang hilang. Dalam solusi Anda sebelumnya, masalah utama bukanlah lebih baik menggunakan "mixin" atau "interface", tetapi, untuk memperbaiki, pertama, model kelas Anda, dan kemudian menggunakan fitur Bahasa Pemrograman yang ada.
sumber
canvas.viewee
dan semua keturunannya memerlukan metode yang disebutpaint()
. Baik kelascommon
maupun tidaksvg
membutuhkannya. Tetapi dalam solusi Anda, hierarki ada dicommon
, tidakcanvas
atausvg
seperti dalam solusi saya 2. Jadi, bagaimana tepatnyapaint()
berakhir di semua subkelascanvas.viewee
jika tidak ada warisan di sana?paint()
harus dipindahkan atau dideklarasikan di "canvas :: viewee". Gagasan pola umum tetap ada, tetapi beberapa anggota mungkin diminta untuk dipindahkan atau diubah.canvas::viewee
?Dalam sebuah artikel berjudul Pola Desain untuk Berurusan dengan Hirarki Warisan Ganda di C ++ , Paman Bob menyajikan solusi yang disebut Stairway to Heaven . Itu menyatakan niat:
Dan diagram menyediakan:
Meskipun dalam solusi 2 tidak ada warisan virtual, itu sangat sesuai dengan pola Stairway to Heaven . Jadi solusi 2 tampaknya masuk akal untuk masalah ini.
sumber