Mengapa menggunakan 'final' di kelas sangat buruk?

34

Saya refactoring situs web warisan PHP OOP.

Saya sangat tergoda untuk mulai menggunakan 'final' di kelas untuk " make it explicit that the class is currently not extended by anything". Ini mungkin menghemat banyak waktu jika saya datang ke kelas dan saya bertanya-tanya apakah saya dapat mengganti nama / menghapus / memodifikasi protectedproperti atau metode. Jika saya benar - benar ingin memperluas kelas saya hanya dapat menghapus kata kunci terakhir untuk membukanya untuk diperpanjang.

Yaitu Jika saya datang ke kelas yang tidak memiliki kelas anak saya dapat mencatat pengetahuan itu dengan menandai kelas sebagai final. Lain kali saya datang ke sana saya tidak perlu mencari kembali basis kode untuk melihat apakah ada anak-anak. Sehingga menghemat waktu saat refactorings.

Itu semua sepertinya ide hemat waktu yang masuk akal .... tapi saya sering membaca bahwa kelas hanya boleh dibuat 'final' pada kesempatan langka / khusus.

Mungkin itu mengacaukan pembuatan objek Mock atau memiliki efek samping lain yang tidak saya pikirkan.

Apa yang saya lewatkan?

JW01
sumber
3
Jika Anda memiliki kendali penuh atas kode Anda, maka itu tidak buruk, saya kira, tetapi sedikit di sisi OCD. Bagaimana jika Anda melewatkan kelas (misalnya tidak memiliki anak tetapi tidak final)? Lebih baik biarkan alat memindai kode dan memberi tahu Anda apakah beberapa kelas memiliki anak. Segera setelah Anda mengemas ini sebagai perpustakaan dan memberikannya kepada orang lain, berurusan dengan 'final' menjadi menyusahkan.
Pekerjaan
Misalnya, MbUnit adalah kerangka kerja terbuka untuk pengujian .Net, dan MsTest tidak (kejutan kejutan). Akibatnya, Anda HARUS membayar keluar 5k ke MSFT hanya karena Anda ingin menjalankan beberapa tes dengan cara MSFT. Pelari tes lain tidak dapat menjalankan dll tes yang dibangun dengan MsTest - semua karena kelas akhir yang digunakan MSFT.
Pekerjaan
3
Beberapa posting blog tentang topik: Kelas Final: Terbuka untuk Perpanjangan, Ditutup untuk Warisan (Mei 2014; oleh Mathias Verraes)
hakre
Artikel yang bagus. Itu berbicara pikiranku tentang ini. Saya tidak tahu tentang anotasi itu sehingga itu berguna. Tepuk tangan.
JW01
"PHP 5 memperkenalkan kata kunci terakhir, yang mencegah kelas anak-anak menimpa metode dengan mengawali definisi dengan final. Jika kelas itu sendiri didefinisikan final maka tidak dapat diperpanjang." php.net/manual/en/language.oop5.final.php Tidak buruk sama sekali.
Hitam

Jawaban:

54

Saya sering membaca bahwa kelas hanya boleh dibuat 'final' pada kesempatan langka / khusus.

Siapa pun yang menulis itu salah. Gunakan dengan finalbebas, tidak ada yang salah dengan itu. Ini mendokumentasikan bahwa kelas tidak dirancang dengan mempertimbangkan warisan, dan ini biasanya berlaku untuk semua kelas secara default: merancang kelas yang dapat diwarisi secara bermakna membutuhkan lebih dari sekadar menghapus finalspecifier; itu membutuhkan banyak perawatan.

Jadi menggunakan finalsecara default sama sekali tidak buruk. Bahkan, banyak orang mengusulkan bahwa ini harus menjadi default, misalnya Jon Skeet .

Mungkin itu mengacaukan pembuatan objek Mock ...

Ini memang peringatan, tetapi Anda selalu bisa menggunakan antarmuka jika Anda perlu mengejek kelas Anda. Ini tentu saja lebih unggul daripada membuat semua kelas terbuka untuk warisan hanya untuk tujuan mengejek.

Konrad Rudolph
sumber
1
1+: Karena mengolok-olok; pertimbangkan untuk membuat antarmuka atau kelas abstrak untuk setiap kelas "final".
Spoike
Nah itu menempatkan kunci pas dalam karya. Kedatangan terlambat ini bertentangan dengan semua jawaban lainnya. Sekarang saya tidak tahu harus percaya apa: o. (Membatalkan jawaban saya yang diterima dan menunda keputusan selama persidangan ulang)
JW01
1
Anda dapat mempertimbangkan Java API itu sendiri dan seberapa sering Anda akan menemukan penggunaan kata kunci 'final'. Bahkan, ini tidak sering digunakan. Ini digunakan dalam kasus dimana kinerja mungkin menjadi buruk karena pengikatan dinamis yang terjadi ketika metode "virtual" dipanggil (di Jawa semua metode virtual jika mereka tidak dinyatakan sebagai 'final' atau 'pribadi'), atau ketika memperkenalkan custom subclass dari kelas Java API mungkin muncul masalah konsistensi, seperti subclassing 'java.lang.String'. String Java adalah objek nilai per definisi dan tidak boleh mengubah nilainya nanti. Subclass dapat melanggar aturan ini.
Jonny Dee
5
@ Jonny Sebagian besar Java API dirancang dengan buruk. Ingatlah bahwa sebagian besar usianya sudah lebih dari sepuluh tahun dan saat ini banyak praktik terbaik telah dikembangkan. Tetapi jangan mengambil kata-kata saya untuk itu: insinyur Jawa sendiri mengatakan begitu, pertama dan terutama Josh Bloch yang telah menulis secara rinci tentang ini, misalnya di Jawa Efektif . Yakinlah bahwa jika Java API dikembangkan hari ini, itu akan terlihat sangat berbeda, dan finalakan memainkan peran yang jauh lebih besar.
Konrad Rudolph
1
Saya masih tidak yakin bahwa menggunakan 'final' lebih sering menjadi praktik terbaik. Menurut pendapat saya, 'final' harus benar-benar digunakan hanya jika persyaratan kinerja menentukannya, atau jika Anda harus memastikan konsistensi tidak dapat dilanggar oleh subkelas. Dalam semua kasus lain, dokumentasi yang diperlukan harus masuk ke dalam, well, dokumentasi (yang perlu ditulis juga), dan tidak ke dalam kode sumber sebagai kata kunci yang menerapkan pola penggunaan tertentu. Bagi saya, itu semacam dewa bermain. Menggunakan anotasi harus menjadi solusi yang lebih baik. Seseorang dapat menambahkan anotasi '@final' dan kompilator dapat mengeluarkan peringatan.
Jonny Dee
8

Jika Anda ingin meninggalkan catatan untuk diri sendiri bahwa suatu kelas tidak memiliki sub-kelas, maka dengan segala cara melakukannya dan menggunakan komentar, itulah gunanya. Kata kunci "final" bukan komentar, dan menggunakan kata kunci bahasa hanya untuk memberi sinyal sesuatu kepada Anda (dan hanya Anda yang akan tahu apa artinya) adalah ide yang buruk.

Jeremy
sumber
17
Ini sepenuhnya salah! “Menggunakan kata kunci bahasa hanya untuk memberi sinyal kepada Anda [...] adalah ide yang buruk.” - Sebaliknya, itulah tepatnya kata kunci bahasa yang ada. Peringatan tentatif Anda, "dan hanya Anda yang akan tahu apa artinya", tentu saja benar tetapi tidak berlaku di sini; OP menggunakan finalseperti yang diharapkan. Tidak ada yang salah dengan itu. Dan menggunakan fitur bahasa untuk menegakkan kendala selalu lebih baik daripada menggunakan komentar.
Konrad Rudolph
8
@Konrad: Kecuali finalharus menyatakan "Tidak boleh ada subclass dari kelas ini yang dibuat" (untuk alasan hukum atau sesuatu), bukan "kelas ini saat ini tidak memiliki anak, jadi saya masih aman untuk mengacaukan dengan anggota yang dilindungi". Maksudnya finaladalah kebalikan dari "diedit secara bebas", dan sebuah finalkelas bahkan tidak boleh memiliki protectedanggota!
SF.
3
@Konrad Rudolph Ini sama sekali tidak kecil. Jika orang lain kemudian ingin memperpanjang kelasnya, ada perbedaan besar antara komentar yang mengatakan "tidak diperpanjang" dan kata kunci yang mengatakan "mungkin tidak diperpanjang".
Jeremy
2
@Konrad Rudolph Kedengarannya seperti pendapat yang bagus untuk mengajukan pertanyaan tentang desain bahasa OOP. Tapi kita tahu cara kerja PHP, dan dibutuhkan pendekatan lain untuk mengizinkan ekstensi kecuali disegel. Berpura-pura tidak apa-apa untuk mengacaukan kata kunci karena "begitulah seharusnya" hanyalah pembelaan diri.
Jeremy
3
@ Jeremy Saya tidak menyatukan kata kunci. Kata kunci finalberarti, “kelas ini tidak diperpanjang [untuk saat ini].” Tidak lebih, tidak kurang. Apakah PHP dirancang dengan filosofi ini dalam pikiran adalah tidak relevan: ia memiliki satu finalkata kunci, setelah semua. Kedua, berdebat dari desain PHP pasti gagal, mengingat bagaimana patchworky dan hanya keseluruhan PHP yang buruk dirancang.
Konrad Rudolph
5

Ada artikel yang bagus tentang "Kapan mendeklarasikan kelas final" . Beberapa kutipan darinya:

TL; DR: Buat kelas Anda selalu final, jika mereka mengimplementasikan antarmuka, dan tidak ada metode publik lainnya yang ditentukan

Kenapa saya harus menggunakan final?

  1. Mencegah rantai warisan kehancuran besar-besaran
  2. Komposisi yang menggembirakan
  3. Paksa pengembang untuk memikirkan API publik pengguna
  4. Paksa pengembang untuk menyusutkan API publik objek
  5. Sebuah finalkelas selalu bisa dibuat bisa diperluas
  6. extends istirahat enkapsulasi
  7. Anda tidak perlu fleksibilitas itu
  8. Anda bebas mengubah kode

Kapan harus menghindari final:

Kelas akhir hanya bekerja secara efektif berdasarkan asumsi berikut:

  1. Ada abstraksi (antarmuka) yang mengimplementasikan kelas akhir
  2. Semua API publik dari kelas akhir adalah bagian dari antarmuka itu

Jika salah satu dari dua pra-kondisi ini tidak ada, maka Anda kemungkinan akan mencapai titik waktu ketika Anda akan membuat kelas dapat diperluas, karena kode Anda tidak benar-benar mengandalkan abstraksi.

PS Terima kasih kepada @ocramius untuk bacaan yang bagus!

Nikita U.
sumber
5

"final" untuk suatu kelas berarti: Anda menginginkan subkelas? Silakan, hapus "final", subklas sebanyak yang Anda suka, tapi jangan mengeluh kepada saya jika itu tidak berhasil. Anda sendirian.

Ketika sebuah kelas dapat disubklasifikasikan, itu perilaku yang diandalkan orang lain, harus dijelaskan dalam istilah abstrak yang dipatuhi subkelas. Penelepon harus ditulis untuk mengharapkan beberapa variasi. Dokumentasi harus ditulis dengan cermat; Anda tidak dapat memberi tahu orang "lihat kode sumber" karena kode sumbernya belum ada. Itu semua usaha. Jika saya tidak berharap bahwa sebuah kelas subkelas, itu adalah upaya yang tidak perlu. "Final" dengan jelas mengatakan bahwa upaya ini belum dilakukan dan memberikan peringatan yang adil.

gnasher729
sumber
-1

Satu hal yang Anda mungkin tidak pikirkan adalah kenyataan bahwa setiap perubahan kelas berarti bahwa ia harus menjalani pengujian QA baru.

Jangan menandai hal-hal sebagai final kecuali Anda benar-benar bersungguh-sungguh.


sumber
Terima kasih. Bisakah Anda jelaskan lebih lanjut. Apakah maksud Anda jika saya menandai kelas sebagai final(perubahan) maka saya hanya perlu mengujinya kembali?
JW01
4
Saya akan mengatakan ini lebih kuat. Jangan menandai hal-hal sebagai final kecuali ekstensi tidak dapat dibuat berfungsi karena desain kelas.
S.Lott
Terima kasih S.Lott - Saya mencoba memahami betapa buruknya penggunaan akhir mempengaruhi kode / proyek. Pernahkah Anda mengalami cerita-cerita horor buruk yang telah menyebabkan Anda menjadi sangat dogmatis tentang penggunaan hemat final. Apakah ini pengalaman langsung?
JW01
1
@ JW01: finalKelas memiliki satu kasus penggunaan utama. Anda memiliki kelas polimorfik yang tidak ingin diperpanjang karena subkelas dapat merusak polimorfisme. Jangan gunakan finalkecuali Anda harus mencegah pembuatan subclass. Selain itu, tidak ada gunanya.
S.Lott
@ JW01, dalam pengaturan produksi Anda mungkin tidak DIIZINKAN untuk menghapus final, karena ini bukan kode yang pelanggan uji dan setujui. Anda mungkin, bagaimanapun, diizinkan untuk menambahkan kode tambahan (untuk pengujian) tetapi Anda mungkin tidak dapat melakukan apa yang Anda inginkan karena Anda tidak dapat memperluas kelas Anda.
-1

Menggunakan 'final' menghilangkan kebebasan orang lain yang ingin menggunakan kode Anda.

Jika kode yang Anda tulis hanya untuk Anda dan tidak akan pernah dirilis ke publik atau pelanggan, maka Anda dapat melakukannya dengan kode yang Anda inginkan, tentu saja. Kalau tidak, Anda mencegah orang lain membuat kode Anda. Terlalu sering saya harus bekerja dengan API yang akan mudah diperluas untuk kebutuhan saya, tetapi kemudian saya terhalang oleh 'final'.

Juga, sering ada kode yang sebaiknya tidak dibuat private, tetapi protected. Tentu, privateberarti "enkapsulasi" dan menyembunyikan hal-hal yang dianggap sebagai detail implementasi. Tetapi sebagai seorang programmer API saya mungkin juga mendokumentasikan fakta bahwa metode xyzdianggap sebagai detail implementasi dan, dengan demikian, dapat diubah / dihapus dalam versi yang akan datang. Jadi setiap orang yang akan bergantung pada kode tersebut terlepas dari peringatan melakukannya dengan risiko sendiri. Tapi dia benar-benar dapat melakukannya dan menggunakan kembali (semoga sudah diuji) kode dan muncul lebih cepat dengan solusi.

Tentu saja, jika implementasi API adalah open source, Anda hanya dapat menghapus metode 'final' atau membuat 'terlindungi', tetapi daripada Anda telah mengubah kode dan perlu melacak perubahan Anda dalam bentuk tambalan.

Namun, jika penerapannya adalah sumber tertutup, Anda akan tertinggal dalam menemukan solusi atau, dalam kasus terburuk, dengan beralih ke API lain dengan sedikit pembatasan mengenai kemungkinan untuk kustomisasi / ekstensi.

Perhatikan bahwa saya tidak menemukan 'final' atau 'pribadi' itu jahat, tetapi saya pikir mereka terlalu sering digunakan karena programmer tidak memikirkan kode-nya dalam hal penggunaan kembali dan ekstensi kode.

Jonny Dee
sumber
2
"Warisan bukan penggunaan kembali kode".
quant_dev
2
Ok, jika Anda bersikeras pada definisi yang sempurna Anda benar. Warisan mengungkapkan hubungan 'is-a' dan fakta inilah yang memungkinkan polimorfisme dan menyediakan cara untuk memperluas API. Namun dalam praktiknya, pewarisan sangat sering digunakan untuk menggunakan kembali kode. Ini khususnya kasus dengan pewarisan berganda. Dan begitu Anda memanggil kode kelas super Anda benar-benar menggunakan kembali kode.
Jonny Dee
@quant_dev: Dapat digunakan kembali dalam OOP pertama-tama berarti polimorf. Dan pewarisan adalah titik kritis untuk perilaku polimorfik (berbagi antarmuka).
Falcon
@Falcon Sharing interface! = Kode berbagi. Setidaknya tidak dalam arti biasa.
quant_dev
3
@quant_dev: Anda mendapatkan reuseability dalam arti OOP salah. Ini berarti bahwa satu metode dapat beroperasi pada banyak tipe data, berkat berbagi antarmuka. Itu adalah bagian utama dari penggunaan kembali kode OOP. Dan warisan secara otomatis mari kita berbagi antarmuka, sehingga meningkatkan penggunaan kembali kode sangat. Itu sebabnya kami berbicara tentang usabilitas dalam OOP. Hanya membuat pustaka dengan rutinitas hampir sama tuanya dengan pemrograman itu sendiri dan dapat dilakukan dengan hampir setiap bahasa, itu biasa. Penggunaan Kembali Kode di OOP lebih dari itu.
Falcon