Saya bermasalah akhir-akhir ini tentang penggunaan kelas abstrak.
Terkadang kelas abstrak dibuat sebelumnya dan berfungsi sebagai templat bagaimana kelas turunan akan bekerja. Itu berarti, lebih atau kurang, bahwa mereka menyediakan beberapa fungsionalitas tingkat tinggi tetapi meninggalkan detail tertentu untuk diimplementasikan oleh kelas turunan. Kelas abstrak mendefinisikan perlunya rincian ini dengan menerapkan beberapa metode abstrak. Dalam kasus seperti itu, kelas abstrak berfungsi seperti cetak biru, deskripsi fungsi tingkat tinggi atau apa pun yang Anda ingin menyebutnya. Itu tidak dapat digunakan sendiri, tetapi harus khusus untuk mendefinisikan rincian yang telah ditinggalkan dari implementasi tingkat tinggi.
Kadang-kadang, kebetulan bahwa kelas abstrak dibuat setelah penciptaan beberapa kelas "turunan" (karena kelas induk / abstrak belum ada, mereka belum diturunkan, tetapi Anda tahu maksud saya). Dalam kasus ini, kelas abstrak biasanya digunakan sebagai tempat di mana Anda dapat meletakkan segala jenis kode umum yang berisi kelas turunan saat ini.
Setelah melakukan pengamatan di atas, saya bertanya-tanya yang mana dari dua kasus ini yang seharusnya menjadi aturan. Haruskah setiap jenis rincian digelembungkan ke kelas abstrak hanya karena mereka saat ini menjadi umum di semua kelas turunan? Haruskah kode umum yang bukan bagian dari fungsionalitas tingkat tinggi ada di sana?
Haruskah kode yang mungkin tidak memiliki makna untuk kelas abstrak itu sendiri ada di sana hanya karena kebetulan umum untuk kelas turunan?
Biarkan saya memberi contoh: Kelas abstrak A memiliki metode a () dan metode abstrak aq (). Metode aq (), di kedua kelas turunan AB dan AC, menggunakan metode b (). Haruskah b () dipindahkan ke A? Jika ya, maka jika seseorang hanya melihat A (mari kita berpura-pura AB dan AC tidak ada di sana), keberadaan b () tidak masuk akal! Apakah ini hal yang buruk? Haruskah seseorang dapat melihat kelas abstrak dan memahami apa yang terjadi tanpa mengunjungi kelas turunannya?
Sejujurnya, pada saat menanyakan ini, saya cenderung percaya bahwa menulis kelas abstrak yang masuk akal tanpa harus melihat di kelas turunannya adalah masalah kode bersih dan arsitektur bersih. Ι tidak terlalu menyukai ide kelas abstrak yang bertindak seperti dump untuk semua jenis kode yang biasa terjadi di semua kelas turunan.
Apa yang Anda pikirkan / praktikkan?
sumber
Writing an abstract class that makes sense without having to look in the derived classes is a matter of clean code and clean architecture.
- Kenapa? Apakah tidak mungkin bahwa, dalam proses mendesain dan mengembangkan aplikasi, saya menemukan bahwa beberapa kelas memiliki fungsi umum yang secara alami dapat di refactored menjadi kelas abstrak? Haruskah saya cukup waskita untuk selalu mengantisipasi ini sebelum menulis kode untuk kelas turunan? Jika saya gagal menjadi lihai ini, apakah saya dilarang melakukan refactoring seperti itu? Haruskah saya membuang kode dan memulai kembali?Jawaban:
Apa yang Anda tanyakan adalah di mana harus menempatkan
b()
, dan, dalam beberapa hal lain pertanyaan adalah apakahA
pilihan terbaik sebagai kelas super langsung untukAB
danAC
.Tampaknya ada tiga pilihan:
b()
keduanyaAB
danAC
ABAC-Parent
yang mewarisi dariA
dan yang memperkenalkanb()
, dan kemudian digunakan sebagai kelas super langsung untukAB
danAC
b()
diA
(tidak tahu apakah kelas masa lainAD
akan inginb()
atau tidak)Sampai kelas lain
AD
yang tidak menginginkanb()
hadiah itu sendiri, (3) sepertinya pilihan yang tepat.Pada saat
AD
hadiah, maka kita dapat menolak untuk pendekatan dalam (2) - setelah semua itu adalah perangkat lunak!sumber
b()
kelas apa pun. Jadikan itu fungsi bebas yang mengambil semua datanya sebagai argumen dan memiliki keduanyaAB
danAC
menyebutnya. Ini berarti Anda tidak perlu memindahkannya atau membuat kelas lagi ketika Anda menambahkanAD
.b()
tidak memerlukan keadaan instance-berumur panjang.ac
yang memanggilb
alih-alihac
abstrak?Kelas abstrak tidak dimaksudkan sebagai tempat pembuangan untuk berbagai fungsi atau data yang dilemparkan ke kelas abstrak karena nyaman.
Satu aturan praktis yang tampaknya memberikan pendekatan berorientasi objek yang paling dapat diandalkan dan diperluas adalah " Lebih suka komposisi daripada warisan ." Kelas abstrak mungkin dianggap sebagai spesifikasi antarmuka yang tidak mengandung kode apa pun.
Jika Anda memiliki beberapa metode yang merupakan jenis metode pustaka yang sejalan dengan kelas abstrak, metode yang merupakan cara yang paling mungkin untuk mengekspresikan beberapa fungsionalitas atau perilaku yang biasanya diperlukan oleh kelas yang berasal dari kelas abstrak, maka masuk akal untuk membuat kelas di antara kelas abstrak dan kelas-kelas lain di mana metode ini tersedia. Kelas baru ini menyediakan contoh khusus dari kelas abstrak yang mendefinisikan alternatif atau jalur implementasi tertentu dengan menyediakan metode ini.
Gagasan kelas abstrak adalah untuk memiliki model abstrak tentang apa yang seharusnya diberikan oleh kelas turunan yang menerapkan kelas abstrak sejauh layanan atau perilaku. Warisan mudah digunakan secara berlebihan dan berkali-kali kelas yang paling berguna terdiri dari berbagai kelas menggunakan pola mixin .
Namun selalu ada pertanyaan perubahan, apa yang akan berubah dan bagaimana itu akan berubah dan mengapa itu akan berubah.
Warisan dapat menyebabkan tubuh sumber rapuh dan rapuh (lihat juga Warisan: cukup hentikan menggunakannya! ).
sumber
b()
adalah fungsi murni. Itu bisa berupa functoid atau templat atau yang lainnya. Saya menggunakan frase "metode perpustakaan" sebagai makna beberapa komponen apakah fungsi murni, objek COM, atau apa pun yang digunakan untuk fungsi yang disediakannya untuk solusi.Pertanyaan Anda menyarankan pendekatan atau ke kelas abstrak. Tapi saya pikir Anda harus menganggapnya sebagai alat lain di kotak alat Anda. Dan kemudian pertanyaannya menjadi: Untuk pekerjaan / masalah mana kelas abstrak merupakan alat yang tepat?
Satu kasus penggunaan yang sangat baik adalah menerapkan pola metode templat . Anda meletakkan semua logika invarian di kelas abstrak dan logika varian di subkelasnya. Perhatikan, bahwa logika bersama itu sendiri tidak lengkap dan tidak berfungsi. Sebagian besar waktu tentang penerapan algoritma, di mana beberapa langkah selalu sama, tetapi setidaknya satu langkah bervariasi. Letakkan satu langkah ini sebagai metode abstrak yang dipanggil dari salah satu fungsi di dalam kelas abstrak.
Saya pikir contoh pertama Anda pada dasarnya adalah deskripsi pola metode templat (koreksi saya jika saya salah), jadi saya akan menganggap ini sebagai kasus penggunaan yang benar-benar valid dari kelas abstrak.
Untuk contoh kedua Anda, saya pikir menggunakan kelas abstrak bukanlah pilihan yang optimal, karena ada metode unggul untuk menangani logika bersama dan kode duplikat. Katakanlah Anda memiliki kelas abstrak
A
, kelas turunanB
danC
dan kedua kelas turunan berbagi beberapa logika dalam bentuk metodes()
. Untuk memutuskan pendekatan yang benar untuk menghilangkan duplikasi, penting untuk diketahui, apakah metodes()
merupakan bagian dari antarmuka publik atau tidak.Jika tidak (seperti dalam contoh nyata Anda sendiri dengan metode
b()
), kasusnya cukup sederhana. Cukup buat kelas terpisah darinya, juga mengekstraksi konteks yang diperlukan untuk melakukan operasi. Ini adalah contoh klasik komposisi atas warisan . Jika ada sedikit atau tidak ada konteks yang dibutuhkan, fungsi pembantu sederhana mungkin sudah cukup seperti yang disarankan dalam beberapa komentar.Jika
s()
merupakan bagian dari antarmuka publik, itu menjadi sedikit lebih rumit. Di bawah asumsi yangs()
tidak ada hubungannya denganB
danC
terkait denganA
, Anda tidak boleh dimasukkan kes()
dalamA
. Jadi di mana meletakkannya? Saya berpendapat untuk mendeklarasikan antarmuka terpisahI
, yang mendefinisikans()
. Kemudian lagi, Anda harus membuat kelas terpisah yang berisi logika untuk mengimplementasikans()
dan keduanyaB
danC
bergantung padanya.Terakhir, berikut adalah tautan ke jawaban yang sangat bagus dari pertanyaan menarik tentang SO yang mungkin membantu Anda memutuskan kapan harus menggunakan antarmuka dan kapan untuk kelas abstrak:
sumber
Sepertinya saya Anda kehilangan titik orientasi objek, baik secara logis dan teknis. Anda menggambarkan dua skenario: pengelompokan perilaku umum jenis dalam kelas dasar dan polimorfisme. Keduanya adalah aplikasi yang sah dari kelas abstrak. Tetapi apakah Anda harus membuat abstrak kelas atau tidak tergantung pada model analitis Anda, bukan kemungkinan teknis.
Anda harus mengenali jenis yang tidak memiliki inkarnasi di dunia nyata, namun meletakkan dasar untuk jenis tertentu yang memang ada. Contoh: seekor binatang. Tidak ada yang namanya binatang. Itu selalu anjing atau kucing atau apa pun tetapi tidak ada binatang yang nyata. Namun Animal membingkai semuanya.
Kemudian Anda berbicara tentang level. Level bukan bagian dari kesepakatan ketika datang ke warisan. Juga tidak mengenali data umum atau perilaku umum, itu adalah pendekatan teknis yang kemungkinan tidak akan membantu. Anda harus mengenali stereotip dan kemudian memasukkan kelas dasar. Jika tidak ada stereotip seperti itu, Anda mungkin lebih baik menggunakan beberapa antarmuka yang diimplementasikan oleh beberapa kelas.
Nama kelas abstrak Anda bersama dengan anggotanya harus masuk akal. Itu harus independen dari setiap kelas turunan, baik secara teknis maupun logis. Seperti Hewan bisa memiliki metode abstrak Makan (yang akan menjadi polimorfik) dan sifat abstrak boolean IsPet dan IsLifestock, yang masuk akal tanpa mengetahui tentang kucing atau anjing atau babi. Setiap ketergantungan (teknis atau logis) harus berjalan satu arah saja: dari keturunan ke basis. Kelas-kelas dasar itu sendiri juga tidak harus memiliki pengetahuan tentang kelas-kelas yang menurun.
sumber
Kasus pertama , dari pertanyaan Anda:
Kasus kedua:
Lalu, pertanyaan Anda :
IMO Anda memiliki dua skenario yang berbeda, karena itu Anda tidak dapat membuat aturan tunggal untuk memutuskan desain apa yang harus diterapkan.
Untuk kasus pertama, kami memiliki hal-hal seperti pola Metode Templat yang telah disebutkan dalam jawaban lain.
Untuk kasus kedua, Anda memberi contoh metode abstrak penerima A kelas ab (); IMO metode b () hanya boleh dipindahkan jika masuk akal untuk SEMUA kelas turunan lainnya. Dengan kata lain, jika Anda memindahkannya karena digunakan di dua tempat, mungkin ini bukan pilihan yang baik, karena besok mungkin ada kelas turunan beton baru di mana b () tidak masuk akal sama sekali. Juga, seperti yang Anda katakan, jika Anda melihat kelas A secara terpisah dan b () juga tidak masuk akal dalam konteks itu, maka mungkin itu petunjuk bahwa ini bukan pilihan desain yang baik juga.
sumber
Saya punya pertanyaan yang persis sama beberapa waktu lalu!
Apa yang saya temukan adalah setiap kali saya menemukan masalah ini saya menemukan bahwa ada beberapa konsep tersembunyi yang belum saya temukan. Dan konsep ini lebih mungkin daripada seharusnya diungkapkan dengan beberapa nilai-objek . Anda memiliki contoh yang sangat abstrak, jadi saya khawatir saya tidak dapat menunjukkan apa yang saya maksud dengan menggunakan kode Anda. Tetapi ini adalah kasus dari praktik saya sendiri. Saya memiliki dua kelas yang mewakili cara untuk mengirim permintaan ke sumber daya eksternal dan mengurai respons. Ada kesamaan dalam bagaimana permintaan dibentuk dan bagaimana tanggapan diuraikan. Jadi seperti itulah hierarki saya:
Kode semacam itu menunjukkan bahwa saya hanya menyalahgunakan warisan. Begitulah cara itu bisa di refactored:
Sejak itu saya memperlakukan warisan dengan sangat hati-hati karena saya sering kali terluka.
Sejak itu saya sampai pada kesimpulan lain tentang warisan. Saya sangat menentang warisan berdasarkan struktur internal . Ini memecah enkapsulasi, rapuh, ini prosedural. Dan ketika saya mendekomposisi domain saya dengan cara yang benar , pewarisan tidak sering terjadi.
sumber