Saat membaca berbagai pertanyaan Stack Overflow dan kode orang lain, konsensus umum tentang cara merancang kelas ditutup. Ini berarti bahwa secara default di Java dan C # semuanya bersifat pribadi, bidang bersifat final, beberapa metode bersifat final, dan terkadang kelas bahkan final .
Gagasan di balik ini adalah untuk menyembunyikan detail implementasi, yang merupakan alasan yang sangat bagus. Namun dengan adanya protected
sebagian besar bahasa OOP dan polimorfisme, ini tidak berhasil.
Setiap kali saya ingin menambah atau mengubah fungsionalitas ke kelas saya biasanya terhalang oleh pribadi dan akhir ditempatkan di mana-mana. Di sini detail implementasi penting: Anda mengambil implementasi dan memperluasnya, mengetahui sepenuhnya apa konsekuensinya. Namun karena saya tidak bisa mendapatkan akses ke bidang dan metode pribadi dan terakhir, saya punya tiga opsi:
- Jangan memperluas kelas, hanya menyelesaikan masalah yang mengarah ke kode yang lebih kompleks
- Salin dan tempel seluruh kelas, matikan penggunaan kembali kode
- Garap proyek
Itu bukan pilihan yang baik. Mengapa tidak protected
digunakan dalam proyek yang ditulis dalam bahasa yang mendukungnya? Mengapa beberapa proyek secara eksplisit melarang pewarisan dari kelas mereka?
sumber
Jawaban:
Merancang kelas agar berfungsi dengan baik saat diperluas, terutama ketika programmer melakukan perluasan tidak sepenuhnya memahami bagaimana kelas seharusnya bekerja, membutuhkan usaha ekstra. Anda tidak bisa hanya mengambil semua yang bersifat pribadi dan menjadikannya publik (atau dilindungi) dan menyebutnya "terbuka." Jika Anda mengizinkan orang lain untuk mengubah nilai variabel, Anda harus mempertimbangkan bagaimana semua nilai yang mungkin akan mempengaruhi kelas. (Bagaimana jika mereka mengatur variabel ke nol? Array kosong? Angka negatif?) Hal yang sama berlaku ketika mengizinkan orang lain untuk memanggil metode. Butuh pemikiran yang cermat.
Jadi tidak terlalu banyak kelas yang seharusnya tidak terbuka, tetapi kadang-kadang itu tidak sepadan dengan upaya untuk membuat mereka terbuka.
Tentu saja, juga mungkin bahwa para penulis perpustakaan hanya malas. Tergantung perpustakaan mana yang Anda bicarakan. :-)
sumber
Membuat semuanya pribadi secara default terdengar kasar, tetapi lihatlah dari sisi lain: Ketika semuanya bersifat pribadi secara default, membuat sesuatu menjadi publik (atau dilindungi, yang merupakan hal yang hampir sama) seharusnya menjadi pilihan sadar; itu adalah kontrak penulis kelas dengan Anda, konsumen, tentang cara menggunakan kelas. Ini adalah kenyamanan bagi Anda berdua: Penulis bebas untuk memodifikasi bagian dalam kelas, selama antarmuka tetap tidak berubah; dan Anda tahu persis bagian mana dari kelas yang dapat Anda andalkan dan mana yang dapat berubah.
Gagasan yang mendasarinya adalah 'kopling longgar' (juga disebut sebagai 'antarmuka sempit'); nilainya terletak pada menjaga kompleksitas tetap rendah. Dengan mengurangi jumlah cara di mana komponen dapat berinteraksi, jumlah ketergantungan silang di antara mereka juga berkurang; dan lintas ketergantungan adalah salah satu jenis kompleksitas terburuk dalam hal pemeliharaan dan manajemen perubahan.
Di perpustakaan yang dirancang dengan baik, kelas-kelas yang layak diperluas melalui warisan telah melindungi dan anggota publik di tempat yang tepat, dan menyembunyikan yang lainnya.
sumber
Segala sesuatu yang tidak pribadi dianggap lebih atau kurang ada dengan perilaku yang tidak berubah di setiap versi kelas yang akan datang. Bahkan, itu dapat dianggap sebagai bagian dari API, didokumentasikan atau tidak. Oleh karena itu, mengekspos terlalu banyak detail mungkin menyebabkan masalah kompatibilitas nanti.
Mengenai resp "final". "segel" kelas, satu kasus khas adalah kelas abadi. Banyak bagian kerangka bergantung pada string yang tidak dapat diubah. Jika kelas String tidak final, akan mudah (bahkan menggoda) untuk membuat subkelas String yang dapat berubah yang menyebabkan semua jenis bug di banyak kelas lain saat digunakan alih-alih kelas String yang tidak dapat diubah.
sumber
final
dan / atauprivate
terkunci pada tempatnya dan tidak pernah dapat diubah .public
anggota;protected
anggota membentuk kontrak hanya antara kelas yang memaparkan mereka dan kelas turunan langsungnya; sebaliknya,public
anggota untuk kontrak dengan semua konsumen atas nama semua kelas warisan yang ada sekarang dan yang akan datang.Di OO ada dua cara untuk menambahkan fungsionalitas ke kode yang ada.
Yang pertama adalah berdasarkan warisan: Anda mengambil kelas dan berasal dari itu. Namun, warisan harus digunakan dengan hati-hati. Anda harus menggunakan warisan publik terutama ketika Anda memiliki hubungan isA antara basis dan kelas turunan (misalnya Rectangle is a Shape). Sebaliknya, Anda harus menghindari warisan publik untuk menggunakan kembali implementasi yang ada. Pada dasarnya, warisan publik digunakan untuk memastikan kelas turunan memiliki antarmuka yang sama dengan kelas dasar (atau yang lebih besar), sehingga Anda dapat menerapkan prinsip substitusi Liskov .
Metode lain untuk menambah fungsionalitas adalah menggunakan delegasi atau komposisi. Anda menulis kelas Anda sendiri yang menggunakan kelas yang ada sebagai objek internal, dan mendelegasikan bagian dari pekerjaan implementasi untuk itu.
Saya tidak yakin apa ide di balik semua final di perpustakaan yang Anda coba gunakan. Mungkin para pemrogram ingin memastikan Anda tidak akan mewarisi kelas baru dari kelas mereka, karena itu bukan cara terbaik untuk memperluas kode mereka.
sumber
Sebagian besar jawabannya benar: Kelas harus disegel (final, NotOverridable, dll) ketika objek tidak dirancang atau dimaksudkan untuk diperpanjang.
Namun, saya tidak akan mempertimbangkan hanya menutup semua desain kode yang tepat. "O" dalam SOLID adalah untuk "Prinsip Terbuka-Tertutup", yang menyatakan bahwa kelas harus "ditutup" untuk modifikasi, tetapi "terbuka" untuk ekstensi. Idenya adalah, Anda memiliki kode dalam suatu objek. Ini bekerja dengan baik. Menambahkan fungsionalitas seharusnya tidak mengharuskan pembukaan kode itu dan membuat perubahan bedah yang dapat merusak perilaku kerja sebelumnya. Sebaliknya, seseorang harus dapat menggunakan pewarisan atau injeksi ketergantungan untuk "memperluas" kelas untuk melakukan hal-hal tambahan.
Sebagai contoh, sebuah kelas "ConsoleWriter" dapat memperoleh teks dan menampilkannya ke konsol. Itu melakukan ini dengan baik. Namun, dalam kasus tertentu Anda JUGA membutuhkan output yang sama ditulis ke file. Membuka kode ConsoleWriter, mengubah antarmuka eksternal untuk menambahkan parameter "WriteToFile" ke fungsi utama, dan menempatkan kode penulisan file tambahan di sebelah kode penulisan konsol akan dianggap sebagai hal yang buruk.
Sebagai gantinya, Anda bisa melakukan salah satu dari dua hal: Anda bisa berasal dari ConsoleWriter untuk membentuk ConsoleAndFileWriter, dan perluas metode Write () untuk memanggil implementasi basis, pertama-tama juga menulis ke file. Atau, Anda bisa mengekstrak antarmuka IWriter dari ConsoleWriter, dan mengimplementasikan kembali antarmuka itu untuk membuat dua kelas baru; FileWriter, dan "MultiWriter" yang dapat diberikan IWriters lain dan akan "menyiarkan" panggilan apa pun yang dibuat untuk metodenya kepada semua penulis yang diberikan. Yang mana yang Anda pilih tergantung pada apa yang akan Anda butuhkan; derivasi sederhana adalah, well, lebih sederhana, tetapi jika Anda memiliki pandangan ke depan yang suram pada akhirnya perlu mengirim pesan ke klien jaringan, tiga file, dua pipa bernama dan konsol, silakan mengambil kesulitan untuk mengekstrak antarmuka dan membuat "Y-adapter"; saya t'
Sekarang, jika fungsi Write () tidak pernah dinyatakan virtual (atau disegel), sekarang Anda dalam masalah jika Anda tidak mengontrol kode sumber. Terkadang bahkan jika Anda melakukannya. Ini pada umumnya posisi yang buruk, dan itu membuat para pengguna API sumber-tertutup atau sumber-terbatas menjadi frustasi ketika itu terjadi. Tapi, ada alasan yang sah mengapa Write (), atau seluruh kelas ConsoleWriter, disegel; mungkin ada informasi sensitif di bidang yang dilindungi (diganti secara bergantian dari kelas dasar untuk memberikan informasi tersebut). Mungkin tidak ada validasi yang memadai; ketika Anda menulis api, Anda harus mengasumsikan programmer yang mengkonsumsi kode Anda tidak lebih pintar atau baik daripada rata-rata "pengguna akhir" dari sistem akhir; dengan begitu kamu tidak pernah kecewa.
sumber
Saya pikir itu mungkin dari semua orang yang menggunakan bahasa oo untuk pemrograman c. Aku melihatmu, java. Jika palu Anda berbentuk seperti objek, tetapi Anda menginginkan sistem prosedural, dan / atau tidak benar - benar memahami kelas, maka proyek Anda perlu dikunci.
Pada dasarnya, jika Anda akan menulis dalam bahasa oo, proyek Anda perlu mengikuti paradigma oo, yang mencakup ekstensi. Tentu saja, jika seseorang menulis perpustakaan dalam paradigma yang berbeda, mungkin Anda tidak seharusnya memperpanjangnya.
sumber
Karena mereka tidak tahu yang lebih baik.
Penulis asli mungkin berpegang teguh pada kesalahpahaman prinsip SOLID yang berasal dari dunia C ++ yang bingung dan rumit.
Saya harap Anda akan melihat bahwa dunia ruby, python, dan perl tidak memiliki masalah yang jawaban di sini diklaim sebagai alasan penyegelan. Perhatikan bahwa pengetikannya ortogonal ke dinamis. Pengubah akses mudah digunakan di sebagian besar (semua?) Bahasa. Kolom C ++ dapat dipermainkan dengan melakukan casting ke beberapa tipe lain (C ++ lebih lemah). Java dan C # dapat menggunakan refleksi. Akses pengubah membuat hal-hal cukup sulit untuk mencegah Anda melakukannya kecuali Anda BENAR-BENAR menginginkannya.
Menyegel kelas dan menandai anggota mana pun secara pribadi melanggar prinsip bahwa hal-hal sederhana harus sederhana dan hal-hal sulit harus dimungkinkan. Tiba-tiba hal-hal yang seharusnya sederhana, bukan.
Saya mendorong Anda untuk mencoba memahami sudut pandang penulis asli. Sebagian besar darinya berasal dari gagasan akademis enkapsulasi yang tidak pernah menunjukkan keberhasilan absolut di dunia nyata. Saya belum pernah melihat kerangka kerja atau pustaka di mana beberapa pengembang di suatu tempat tidak berharap itu bekerja sedikit berbeda dan tidak punya alasan yang baik untuk mengubahnya. Ada dua kemungkinan yang mungkin menjangkiti pengembang perangkat lunak asli yang menyegel dan menjadikan anggota sebagai pribadi.
Saya pikir dalam kerangka kerja perusahaan, # 2 mungkin demikian. Kerangka kerja C ++, Java dan .NET harus "selesai" dan harus mengikuti panduan tertentu. Pedoman tersebut biasanya berarti tipe tersegel kecuali tipe tersebut secara eksplisit dirancang sebagai bagian dari hierarki tipe dan anggota pribadi untuk banyak hal yang mungkin berguna untuk digunakan orang lain .. tetapi karena mereka tidak secara langsung berhubungan dengan kelas itu, mereka tidak terkena . Mengekstraksi tipe baru akan terlalu mahal untuk didukung, didokumentasikan, dll ...
Seluruh ide di balik pengubah akses adalah bahwa pemrogram harus dilindungi dari diri mereka sendiri. "Pemrograman C buruk karena memungkinkan Anda menembak diri sendiri." Ini bukan filosofi yang saya setujui sebagai programmer.
Saya lebih suka pendekatan mangling nama python. Anda dapat dengan mudah (jauh lebih mudah daripada refleksi) mengganti kemaluan jika Anda membutuhkan. Langgan yang bagus tersedia di sini: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/
Pengubah pribadi Ruby sebenarnya lebih seperti dilindungi dalam C # dan tidak memiliki pribadi sebagai pengubah C #. Dilindungi sedikit berbeda. Ada banyak penjelasan di sini: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
Ingat, bahasa statis Anda tidak harus sesuai dengan gaya kuno kode writte di masa lalu bahasa itu.
sumber
EDIT: Saya percaya kelas harus dirancang agar terbuka. Dengan beberapa batasan, tetapi tidak boleh ditutup untuk warisan.
Mengapa: "Kelas akhir" atau "kelas tertutup" bagi saya terasa aneh. Saya tidak pernah harus menandai salah satu kelas saya sebagai "final" ("disegel"), karena saya mungkin harus mewarisi kelas itu, nanti.
Saya telah membeli / mengunduh perpustakaan pihak ketiga dengan kelas, (sebagian besar kontrol visual), dan sangat berterima kasih tidak ada perpustakaan yang menggunakan "final", bahkan jika didukung oleh bahasa pemrograman, di mana mereka diberi kode, karena saya akhirnya memperluas kelas-kelas itu, bahkan jika saya telah mengkompilasi perpustakaan tanpa kode sumber.
Kadang-kadang, saya harus berurusan dengan beberapa kelas "disegel", dan akhirnya membuat kelas baru "pembungkus" yang bersaing dengan kelas yang diberikan, yang memiliki anggota yang serupa.
Clasifiers lingkup memungkinkan untuk "menyegel" beberapa fitur tanpa membatasi pewarisan.
Ketika saya beralih dari Structured Progr. untuk OOP, saya mulai menggunakan anggota kelas privat , tetapi, setelah banyak proyek, saya akhirnya menggunakan yang dilindungi , dan, akhirnya, mempromosikan properti atau metode itu ke publik , jika perlu.
PS Saya tidak suka "final" sebagai kata kunci dalam C #, saya lebih suka menggunakan "disegel" seperti Java, atau "steril", mengikuti metafora pewarisan. Selain itu, "final" adalah kata ambiguos yang digunakan dalam beberapa konteks.
sumber