Operator C # is
dan operator Java instanceof
memungkinkan Anda untuk bercabang pada antarmuka (atau, lebih kasarnya, kelas dasarnya) yang telah diterapkan oleh instance objek.
Apakah pantas untuk menggunakan fitur ini untuk percabangan tingkat tinggi berdasarkan kemampuan yang disediakan oleh antarmuka?
Atau haruskah kelas dasar menyediakan variabel boolean untuk menyediakan antarmuka untuk menggambarkan kemampuan yang dimiliki objek?
Contoh:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
vs.
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Apakah ada kesulitan untuk menggunakan implementasi antarmuka untuk identifikasi kemampuan?
Contoh ini hanya itu, contoh. Saya bertanya-tanya tentang aplikasi yang lebih umum.
object-oriented
TheCatWhisperer
sumber
sumber
CanResetPassword
hanya akan benar ketika sesuatu diterapkanIResetsPassword
. Sekarang Anda membuat properti menentukan sesuatu yang harus dikontrol oleh sistem tipe (dan pada akhirnya akan ketika Anda membentuk sebelumnya para pemain).Jawaban:
Dengan peringatan bahwa yang terbaik adalah menghindari downcasting jika memungkinkan, maka bentuk pertama lebih disukai karena dua alasan:
is
kebakaran maka downcast pasti akan berfungsi. Di formulir kedua, Anda perlu memastikan secara manual bahwa hanya objek yang mengimplementasikanIResetsPassword
return true untuk properti ituItu bukan untuk mengatakan Anda tidak bisa memperbaiki masalah ini. Anda bisa, misalnya, memiliki kelas dasar memiliki satu set antarmuka yang diterbitkan yang dapat Anda periksa inklusi. Tapi Anda benar-benar hanya secara manual menerapkan sebagian dari sistem tipe yang ada yang berlebihan.
BTW preferensi alami saya adalah komposisi daripada warisan tetapi sebagian besar berkaitan dengan negara. Warisan antarmuka tidak seburuk biasanya. Juga, jika Anda menemukan diri Anda menerapkan hierarki tipe orang miskin, Anda lebih baik menggunakan yang sudah ada karena kompiler akan membantu Anda.
sumber
(account as IResettable)?.ResetPassword();
atauvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Kemampuan suatu objek tidak boleh diidentifikasi sama sekali.
Klien yang menggunakan objek tidak harus diminta untuk mengetahui apa pun tentang cara kerjanya. Klien seharusnya hanya mengetahui hal-hal yang bisa dilakukan objek. Apa yang dilakukan objek, setelah diberitahu, bukanlah masalah klien.
Jadi, bukannya
atau
mempertimbangkan
atau
karena
account
sudah tahu kalau ini akan berhasil. Kenapa bertanya itu? Katakan saja apa yang Anda inginkan dan biarkan ia melakukan apa pun yang akan dilakukan.Bayangkan ini dilakukan sedemikian rupa sehingga klien tidak peduli apakah itu berhasil atau tidak karena itu adalah masalah lain. Pekerjaan klien hanya untuk melakukan upaya. Sudah dilakukan sekarang dan ada hal-hal lain yang harus dihadapi. Gaya ini memiliki banyak nama tetapi nama yang paling saya sukai adalah kirim, jangan tanya .
Sangat menggoda ketika menulis klien untuk berpikir Anda harus melacak semuanya dan menarik semua yang Anda pikir perlu Anda ketahui. Ketika Anda melakukan itu, Anda mengubah objek ke dalam. Hargai ketidaktahuan Anda. Dorong detailnya dan biarkan benda itu menanganinya. Semakin sedikit Anda tahu yang lebih baik.
sumber
if (!account.AttemptPasswordReset()) /* attempt failed */;
Dengan cara ini klien mengetahui hasilnya tetapi menghindari beberapa masalah dengan logika keputusan dalam pertanyaan. Jika upaya ini tidak sinkron, Anda akan menerima panggilan balik.account
ketika dibangun. Popup, logging, printouts, dan peringatan audio dapat terjadi pada saat ini. Tugas klien adalah mengatakan "lakukan sekarang". Tidak harus melakukan lebih dari itu. Anda tidak harus melakukan semuanya di satu tempat.Latar Belakang
Warisan adalah alat yang ampuh yang melayani tujuan dalam pemrograman berorientasi objek. Namun, itu tidak menyelesaikan setiap masalah dengan elegan: kadang-kadang, solusi lain lebih baik.
Jika Anda berpikir kembali ke kelas awal ilmu komputer Anda (dengan asumsi Anda memiliki gelar CS), Anda mungkin ingat seorang profesor memberi Anda paragraf yang menyatakan apa yang pelanggan inginkan untuk dilakukan oleh perangkat lunak. Tugas Anda adalah membaca paragraf, mengidentifikasi aktor dan tindakan, dan keluar dengan garis besar kasar tentang apa kelas dan metode itu. Akan ada beberapa lead gelandangan di sana yang terlihat seperti mereka penting, tetapi tidak. Ada kemungkinan yang sangat nyata untuk salah menafsirkan persyaratan.
Ini adalah keterampilan penting yang bahkan salah satu dari kita yang paling berpengalaman pun salah: mengidentifikasi persyaratan dengan tepat dan menerjemahkannya ke dalam bahasa mesin.
Pertanyaanmu
Berdasarkan apa yang telah Anda tulis, saya pikir Anda mungkin salah memahami kasus umum tindakan opsional yang dapat dilakukan oleh kelas. Ya, saya tahu kode Anda hanyalah contoh dan Anda tertarik dengan kasus umum. Namun, sepertinya Anda ingin tahu cara menangani situasi di mana subtipe tertentu dari suatu objek dapat melakukan suatu tindakan, tetapi subtipe lainnya tidak bisa.
Hanya karena objek seperti
account
memiliki jenis akun tidak berarti yang diterjemahkan ke dalam jenis dalam bahasa OO. "Ketik" dalam bahasa manusia tidak selalu berarti "kelas." Dalam konteks akun, "ketik" mungkin lebih dekat berkorelasi dengan "set izin." Anda ingin menggunakan akun pengguna untuk melakukan suatu tindakan, tetapi tindakan itu mungkin atau mungkin tidak dapat dilakukan oleh akun itu. Daripada menggunakan warisan, saya akan menggunakan token delegasi atau keamanan.Solusi saya
Pertimbangkan kelas akun yang memiliki beberapa tindakan opsional yang dapat dilakukan. Alih-alih mendefinisikan "dapat melakukan tindakan X" melalui warisan, mengapa akun tidak mengembalikan objek yang didelegasikan (penyetel ulang kata sandi, submitter formulir, dll) atau token akses?
Manfaat untuk opsi terakhir ada bagaimana jika saya ingin menggunakan kredensial akun saya untuk mengatur ulang kata sandi orang lain ?
Tidak hanya sistem yang lebih fleksibel, saya tidak hanya menghapus gips, tetapi desainnya lebih ekspresif . Saya bisa membaca ini dan dengan mudah memahami apa yang dilakukannya dan bagaimana itu dapat digunakan.
TL; DR: ini berbunyi seperti masalah XY . Umumnya ketika dihadapkan dengan dua pilihan yang suboptimal, perlu mengambil kembali langkah dan pemikiran "apa aku benar-benar capai di sini? Haruskah saya benar-benar berpikir tentang bagaimana membuat typecasting kurang jelek, atau saya harus mencari cara untuk hapus typecast sepenuhnya? "
sumber
account.getPasswordResetter().resetPassword();
.The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?
.. mudah. Anda membuat objek domain Akun untuk akun lain dan menggunakannya. Kelas statis bermasalah untuk pengujian unit (metode yang menurut definisi memerlukan akses ke beberapa penyimpanan, jadi sekarang Anda tidak dapat dengan mudah menguji unit kode apa pun yang menyentuh kelas itu lagi) dan sebaiknya dihindari. Pilihan untuk mengembalikan beberapa antarmuka lain seringkali merupakan ide yang bagus tetapi tidak benar-benar berurusan dengan masalah mendasar (Anda baru saja memindahkan masalah ke antarmuka lain).Bill Venner mengatakan bahwa pendekatan Anda baik - baik saja ; lompat ke bagian berjudul 'When to Use instanceof'. Anda downcasting ke tipe / antarmuka tertentu yang hanya mengimplementasikan perilaku spesifik itu sehingga Anda benar untuk selektif tentang hal itu.
Namun, jika Anda ingin menggunakan pendekatan lain, ada banyak cara untuk mencukur yak.
Ada pendekatan polimorfisme; Anda dapat berargumen bahwa semua akun memiliki kata sandi, oleh karena itu semua akun setidaknya dapat mencoba mengatur ulang kata sandi. Ini berarti, dalam kelas akun dasar, kita harus memiliki
resetPassword()
metode yang diterapkan semua akun tetapi dengan caranya sendiri. Pertanyaannya adalah bagaimana metode itu harus berperilaku ketika kemampuannya tidak ada.Itu bisa mengembalikan kekosongan dan diam-diam menyelesaikannya apakah mereset kata sandi atau tidak, mungkin mengambil tanggung jawab untuk mencetak pesan secara internal jika tidak mengatur ulang kata sandi. Bukan ide terbaik.
Itu bisa mengembalikan boolean yang menunjukkan apakah reset berhasil. Mengaktifkan boolean itu, kita dapat menghubungkan bahwa pengaturan ulang kata sandi gagal.
Itu bisa mengembalikan sebuah String yang menunjukkan hasil dari upaya pengaturan ulang kata sandi. String dapat memberikan detail lebih lanjut tentang mengapa reset gagal dan bisa menjadi output.
Itu bisa mengembalikan objek ResetResult yang menyampaikan lebih detail dan menggabungkan semua elemen pengembalian sebelumnya.
Ini bisa mengembalikan kekosongan dan malah membuang pengecualian jika Anda mencoba mengatur ulang akun yang tidak memiliki kemampuan itu (jangan lakukan ini karena menggunakan penanganan pengecualian untuk kontrol aliran normal adalah praktik yang buruk karena berbagai alasan).
Memiliki
canResetPassword()
metode yang cocok mungkin tidak tampak seperti hal terburuk di dunia karena secara teori itu adalah kemampuan statis bawaan kelas ketika ditulis. Ini adalah petunjuk mengapa pendekatan metode adalah ide yang buruk, karena itu menunjukkan bahwa kemampuan itu dinamis dan yangcanResetPassword()
mungkin berubah, yang juga menciptakan kemungkinan tambahan bahwa itu mungkin berubah antara meminta izin dan membuat panggilan. Seperti disebutkan di tempat lain, beri tahu daripada meminta izin.Komposisi atas warisan bisa menjadi pilihan: Anda bisa memiliki
passwordResetter
bidang terakhir (atau pengambil yang setara) dan kelas yang setara yang dapat Anda periksa untuk null sebelum memanggilnya. Sementara itu bertindak sedikit seperti meminta izin, finalitas mungkin menghindari sifat dinamis yang disimpulkan.Anda mungkin berpikir untuk mengeksternalkan fungsionalitas ke dalam kelasnya sendiri yang dapat mengambil akun sebagai parameter dan menindaklanjutinya (misalnya
resetter.reset(account)
), meskipun ini juga sering merupakan praktik buruk (analogi yang umum adalah penjaga toko merogoh dompet Anda untuk mendapatkan uang tunai).Namun, dalam bahasa dengan kapabilitas, Anda mungkin menggunakan mixin atau ciri, Anda berakhir di posisi di mana Anda mulai di mana Anda mungkin memeriksa keberadaan kapabilitas tersebut.
sumber
Untuk contoh khusus " dapat mengatur ulang kata sandi " ini, saya sarankan menggunakan komposisi di atas warisan (dalam hal ini, warisan antarmuka / kontrak). Karena, dengan melakukan ini:
Anda segera menentukan (pada waktu kompilasi) bahwa kelas Anda ' dapat mengatur ulang kata sandi '. Tetapi, jika dalam skenario Anda, keberadaan kapabilitas bersyarat dan tergantung pada hal-hal lain, maka Anda tidak dapat menentukan hal-hal pada waktu kompilasi lagi. Kemudian, saya sarankan melakukan ini:
Sekarang, saat runtime, Anda dapat memeriksa apakah
myFoo.passwordResetter != null
sebelum melakukan operasi ini. Jika Anda ingin memisahkan barang lebih banyak (dan Anda berencana untuk menambahkan lebih banyak kemampuan), Anda dapat:MEMPERBARUI
Setelah saya membaca beberapa jawaban dan komentar dari OP saya mengerti pertanyaannya bukan tentang solusi komposisional. Jadi saya pikir pertanyaannya adalah tentang bagaimana mengidentifikasi kemampuan objek secara lebih baik, dalam skenario seperti di bawah ini:
Sekarang, saya akan mencoba menganalisis semua opsi yang disebutkan:
OP contoh kedua:
Katakanlah kode ini menerima beberapa kelas akun dasar (misalnya:
BaseAccount
dalam contoh saya); ini buruk karena memasukkan boolean di kelas dasar, mencemarinya dengan kode yang tidak masuk akal untuk berada di sana.OP contoh pertama:
Untuk menjawab pertanyaan, ini lebih tepat daripada opsi sebelumnya, tetapi tergantung pada implementasinya akan melanggar prinsip L solid, dan mungkin memeriksa seperti ini akan menyebar melalui kode dan membuat pemeliharaan lebih lanjut lebih sulit.
Pelamar CandiedOrange:
Jika
ResetPassword
metode ini dimasukkan dalamBaseAccount
kelas, maka itu juga mencemari kelas dasar dengan kode yang tidak sesuai, seperti pada contoh kedua OPJawaban Snowman:
Ini adalah solusi yang baik, tetapi mempertimbangkan bahwa kemampuannya dinamis (dan mungkin berubah seiring waktu). Namun, setelah saya membaca beberapa komentar dari OP, saya kira pembicaraan di sini adalah mengenai polimorfisme dan kelas-kelas yang ditentukan secara statis (walaupun contoh akun secara intuitif menunjukkan skenario dinamis). EG: dalam
AccountManager
contoh ini pemeriksaan untuk izin adalah permintaan ke DB; dalam pertanyaan OP pemeriksaan adalah upaya casting benda.Saran lain dari saya:
Gunakan pola Metode Templat untuk percabangan tingkat tinggi. Hirarki kelas yang disebutkan tetap seperti itu; kami hanya membuat penangan yang lebih tepat untuk objek, untuk menghindari gips dan properti / metode yang tidak sesuai, membuat polusi pada kelas dasar.
Anda dapat mengikat operasi ke kelas akun yang sesuai dengan menggunakan kamus / hashtable, atau melakukan operasi run-time menggunakan metode ekstensi, menggunakan
dynamic
kata kunci, atau sebagai opsi terakhir gunakan hanya satu pemain untuk meneruskan objek akun ke operasi (di kasus ini jumlah gips hanya satu, di awal operasi).sumber
Tak satu pun dari opsi yang Anda sajikan adalah OO yang baik. Jika Anda menulis pernyataan if di sekitar jenis objek, Anda kemungkinan besar melakukan kesalahan OO (ada pengecualian, ini bukan salah.) Berikut ini jawaban OO sederhana untuk pertanyaan Anda (mungkin tidak valid C #):
Saya telah memodifikasi contoh ini agar sesuai dengan apa yang kode asli lakukan. Berdasarkan beberapa komentar, tampaknya orang-orang terpaku pada apakah ini kode khusus 'benar' di sini. Itu bukan poin dari contoh ini. Seluruh overloading Polymorphic pada dasarnya untuk secara kondisional melaksanakan implementasi logika yang berbeda berdasarkan pada jenis objek. Apa yang Anda lakukan dalam kedua contoh tersebut adalah mengacaukan apa yang diberikan oleh bahasa Anda sebagai fitur. Singkatnya, Anda dapat menyingkirkan sub-tipe dan menempatkan kemampuan untuk mengatur ulang sebagai properti boolean dari tipe Akun (mengabaikan fitur-fitur lain dari sub-tipe.)
Tanpa pandangan yang lebih luas dari desain, tidak mungkin untuk mengatakan apakah ini merupakan solusi yang baik untuk sistem khusus Anda. Ini sederhana dan jika itu berfungsi untuk apa yang Anda lakukan, Anda kemungkinan besar tidak akan perlu terlalu memikirkannya lagi kecuali seseorang gagal memeriksa CanResetPassword () sebelum memanggil ResetPassword (). Anda juga dapat mengembalikan boolean atau gagal secara diam-diam (tidak disarankan). Itu benar-benar tergantung pada spesifikasi desain.
sumber
NotResetable
. "mungkin sebuah akun memiliki lebih banyak fungsi daripada dapat diatur ulang" Saya harapkan begitu tetapi tampaknya tidak penting untuk pertanyaan yang ada.Menjawab
Jawaban saya yang sedikit beralasan untuk pertanyaan Anda adalah:
Tambahan:
Rambling
Saya melihat bahwa Anda tidak menambahkan
c#
tag, tetapi bertanya dalamobject-oriented
konteks umum .Apa yang Anda gambarkan adalah teknis dari bahasa yang diketik statis / kuat, dan tidak spesifik "OO" pada umumnya. Masalah Anda berasal, antara lain, dari kenyataan bahwa Anda tidak dapat memanggil metode dalam bahasa tersebut tanpa memiliki tipe yang cukup sempit pada waktu kompilasi (baik melalui definisi variabel, parameter metode / definisi nilai pengembalian, atau pemeran eksplisit). Saya telah melakukan kedua varian Anda di masa lalu, dalam jenis bahasa ini, dan keduanya tampak jelek bagi saya akhir-akhir ini.
Dalam bahasa OO yang diketik secara dinamis, masalah ini tidak terjadi karena konsep yang disebut "bebek mengetik". Singkatnya, ini berarti pada dasarnya Anda tidak mendeklarasikan jenis objek di mana saja (kecuali saat membuatnya). Anda mengirim pesan ke objek, dan objek menangani pesan itu. Anda tidak peduli apakah objek tersebut benar-benar memiliki metode nama itu. Beberapa bahasa bahkan memiliki metode umum, catch-all
method_missing
yang membuat ini lebih fleksibel.Jadi, dalam bahasa seperti itu (Ruby, misalnya), kode Anda menjadi:
Periode.
The
account
baik akan me-reset password, melempar "ditolak" pengecualian, atau melempar "tidak tahu bagaimana menangani 'reset_password'" pengecualian.Jika Anda perlu bercabang secara eksplisit untuk alasan apa pun, Anda masih akan melakukannya pada kapabilitas, bukan pada kelas:
(Di mana
can?
hanya metode yang memeriksa apakah objek dapat menjalankan beberapa metode, tanpa memanggilnya.)Perhatikan bahwa sementara preferensi pribadi saya saat ini menggunakan bahasa yang diketik secara dinamis, ini hanya preferensi pribadi. Bahasa yang diketik secara statis juga memiliki banyak hal untuk mereka, jadi tolong jangan menganggap ocehan ini sebagai bash terhadap bahasa yang diketik secara statis ...
sumber
Tampaknya pilihan Anda berasal dari berbagai kekuatan motivasi. Yang pertama tampaknya adalah hack yang digunakan karena pemodelan objek yang buruk. Ini menunjukkan bahwa abstraksi Anda salah, karena mereka merusak polimorfisme. Lebih mungkin daripada tidak melanggar prinsip Open Open , yang menghasilkan kopling yang lebih ketat.
Pilihan kedua Anda bisa saja baik dari perspektif OOP, tetapi tipe casting membingungkan saya. Jika akun Anda memungkinkan Anda untuk mereset kata sandi, mengapa Anda perlu mengetik ulang? Jadi dengan asumsi bahwa
CanResetPassword
klausa Anda mewakili beberapa persyaratan bisnis mendasar yang merupakan bagian dari abstraksi akun, opsi kedua Anda OK.sumber