Jangan mendeklarasikan antarmuka untuk objek yang tidak dapat diubah
[EDIT] Di mana objek yang dimaksud mewakili Data Transfer Objects (DTOs) atau Data Lama Biasa (PODs)
Apakah itu pedoman yang masuk akal?
Hingga kini, saya sering membuat antarmuka untuk kelas tersegel yang tidak dapat diubah (data tidak dapat diubah). Saya sudah mencoba berhati-hati untuk tidak menggunakan antarmuka di mana pun saya peduli tentang ketidakberubahan.
Sayangnya, antarmuka mulai meresapi kode (dan bukan hanya kode saya yang saya khawatirkan). Anda akhirnya lulus antarmuka, dan kemudian ingin meneruskannya ke beberapa kode yang benar-benar ingin mengasumsikan bahwa hal yang diteruskan ke itu tidak dapat diubah.
Karena masalah ini, saya mempertimbangkan untuk tidak pernah mendeklarasikan antarmuka untuk objek yang tidak dapat diubah.
Ini mungkin memiliki konsekuensi sehubungan dengan Unit Testing, tetapi selain itu, apakah ini tampak sebagai pedoman yang masuk akal?
Atau apakah ada pola lain yang harus saya gunakan untuk menghindari masalah "penyebaran antarmuka" yang saya lihat?
(Saya menggunakan objek yang tidak dapat diubah ini karena beberapa alasan: Terutama untuk keamanan utas karena saya menulis banyak kode multi-utas; tetapi juga karena itu berarti saya dapat menghindari membuat salinan defensif objek yang diteruskan ke metode. Kode menjadi lebih sederhana dalam banyak kasus ketika Anda mengetahui sesuatu tidak dapat diubah - yang tidak Anda lakukan jika Anda telah diberi antarmuka. Bahkan, seringkali Anda bahkan tidak dapat membuat salinan defensif dari objek yang dirujuk melalui antarmuka jika itu tidak memberikan operasi klon atau cara serialisasi ...)
[EDIT]
Untuk memberikan lebih banyak konteks karena alasan saya ingin membuat objek tidak berubah, lihat posting blog ini dari Eric Lippert:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
Saya juga harus menunjukkan bahwa saya sedang bekerja dengan beberapa konsep tingkat rendah di sini, seperti item yang sedang dimanipulasi / diedarkan dalam antrian pekerjaan multi-threaded. Ini pada dasarnya adalah DTO.
Juga Joshua Bloch merekomendasikan penggunaan benda-benda abadi dalam bukunya Java Efektif .
Mengikuti
Terima kasih atas umpan baliknya, semuanya. Saya telah memutuskan untuk terus maju dan menggunakan pedoman ini untuk DTO dan sejenisnya. Sejauh ini sudah bekerja dengan baik, tapi baru seminggu ... Tetap saja, ini terlihat bagus.
Ada beberapa masalah lain yang berkaitan dengan ini yang ingin saya tanyakan; terutama sesuatu yang saya sebut "Kerusakan Dalam atau Dangkal" (nomenklatur yang saya curi dari kloning Deep dan Dangkal) - tapi itu pertanyaan lain kali.
sumber
List<Number>
yang dapat menampungInteger
,Float
,Long
,BigDecimal
, dll ... Semua yang berubah sendiri.Jawaban:
Menurut pendapat saya, aturan Anda adalah yang baik (atau setidaknya itu bukan yang buruk), tetapi hanya karena situasi yang Anda gambarkan. Saya tidak akan mengatakan bahwa saya setuju dengan itu dalam semua situasi, jadi, dari sudut pandang pedant batin saya, saya harus mengatakan aturan Anda secara teknis terlalu luas.
Biasanya Anda tidak akan mendefinisikan objek tidak berubah kecuali mereka pada dasarnya digunakan sebagai objek transfer data (DTO), yang berarti bahwa mereka berisi properti data tetapi sangat sedikit logika dan tidak ada ketergantungan. Jika itu masalahnya, seperti yang terlihat di sini, saya akan mengatakan Anda aman untuk menggunakan jenis beton secara langsung daripada antarmuka.
Saya yakin akan ada beberapa puritan unit-test yang akan tidak setuju, tetapi menurut saya, kelas DTO dapat dengan aman dikeluarkan dari persyaratan unit-testing dan dependensi-injeksi. Tidak perlu menggunakan pabrik untuk membuat DTO, karena tidak memiliki ketergantungan. Jika semuanya membuat DTO secara langsung sesuai kebutuhan, maka sebenarnya tidak ada cara untuk menyuntikkan tipe yang berbeda, jadi tidak perlu antarmuka. Dan karena mereka tidak mengandung logika, tidak ada yang perlu diuji unit. Bahkan jika mereka memang mengandung beberapa logika, selama mereka tidak memiliki dependensi, maka itu harus sepele untuk unit-test logika, jika perlu.
Karena itu, saya pikir membuat aturan bahwa semua kelas DTO tidak akan mengimplementasikan antarmuka, meskipun berpotensi tidak perlu, tidak akan mengganggu desain perangkat lunak Anda. Karena Anda memiliki persyaratan bahwa data harus tetap, dan Anda tidak dapat memaksakannya melalui antarmuka, maka saya akan mengatakan itu benar-benar sah untuk menetapkan aturan itu sebagai standar pengkodean.
Masalah yang lebih besar, adalah kebutuhan untuk secara ketat menegakkan lapisan DTO yang bersih. Selama kelas Anda yang tidak berubah antarmuka hanya ada di lapisan DTO, dan lapisan DTO Anda tetap bebas dari logika dan dependensi, maka Anda akan aman. Jika Anda mulai mencampur lapisan Anda, dan Anda memiliki kelas tanpa antarmuka yang berfungsi ganda sebagai kelas lapisan bisnis, maka saya pikir Anda akan mulai mengalami banyak masalah.
sumber
Saya biasa membuat keributan besar tentang membuat kode saya kebal terhadap penyalahgunaan. Saya membuat antarmuka read-only untuk menyembunyikan anggota yang bermutasi, menambahkan banyak kendala pada tanda tangan generik saya, dll., Ternyata sebagian besar waktu saya membuat keputusan desain karena saya tidak mempercayai rekan kerja imajiner saya. "Mungkin suatu hari nanti mereka akan merekrut pria entry-level baru dan dia tidak akan tahu bahwa kelas XYZ tidak dapat memperbarui DTO ABC. Oh tidak!" Di lain waktu saya berfokus pada masalah yang salah - mengabaikan solusi yang jelas - tidak melihat hutan melalui pepohonan.
Saya tidak pernah membuat antarmuka untuk DTO saya lagi. Saya bekerja dengan asumsi bahwa orang yang menyentuh kode saya (terutama saya sendiri) tahu apa yang diperbolehkan dan apa yang masuk akal. Jika saya terus membuat kesalahan bodoh yang sama, saya biasanya tidak mencoba mengeraskan antarmuka saya. Sekarang saya menghabiskan sebagian besar waktu saya untuk mencoba memahami mengapa saya terus membuat kesalahan yang sama. Biasanya karena saya terlalu menganalisis sesuatu atau melewatkan konsep kunci. Kode saya jauh lebih mudah untuk dikerjakan sejak saya menyerah menjadi paranoid. Saya juga berakhir dengan lebih sedikit "kerangka kerja" yang membutuhkan pengetahuan orang dalam untuk bekerja pada sistem.
Kesimpulan saya adalah menemukan hal paling sederhana yang berhasil. Kompleksitas tambahan membuat antarmuka yang aman hanya membuang waktu pengembangan dan menyulitkan kode sederhana. Khawatir tentang hal-hal seperti ini ketika Anda memiliki 10.000 pengembang menggunakan perpustakaan Anda. Percayalah, itu akan menyelamatkan Anda dari banyak ketegangan yang tidak perlu.
sumber
Sepertinya panduan yang oke, tapi untuk alasan aneh. Saya sudah memiliki sejumlah tempat di mana antarmuka (atau kelas dasar abstrak) menyediakan akses seragam ke serangkaian objek yang tidak dapat diubah. Strategi cenderung jatuh di sini. Benda-benda negara cenderung jatuh di sini. Saya tidak berpikir itu terlalu tidak masuk akal untuk membentuk antarmuka agar tampak tidak berubah dan mendokumentasikannya di API Anda.
Yang mengatakan, orang cenderung over-antarmuka objek Data Lama Plain (selanjutnya, POD), dan bahkan struktur sederhana (sering tidak berubah). Jika kode Anda tidak memiliki alternatif yang waras untuk beberapa struktur fundamental, ia tidak memerlukan antarmuka. Tidak, pengujian unit bukan alasan yang cukup untuk mengubah desain Anda (mengejek akses database bukan alasan Anda menyediakan antarmuka untuk itu, itu adalah fleksibilitas untuk perubahan di masa mendatang) - itu bukan akhir dunia jika pengujian Anda gunakan struktur dasar dasar apa adanya.
sumber
Saya pikir itu bukan masalah yang harus dikhawatirkan oleh pelakunya. Jika
interface X
dimaksudkan untuk tidak berubah, bukankah itu tanggung jawab pelaksana antarmuka untuk memastikan bahwa mereka mengimplementasikan antarmuka dengan cara yang tidak berubah?Namun, dalam pikiran saya, tidak ada yang namanya antarmuka tidak dapat diubah - kontrak kode yang ditentukan oleh antarmuka hanya berlaku untuk metode objek yang terbuka, dan tidak ada apa pun tentang internal objek.
Jauh lebih umum untuk melihat imutabilitas diimplementasikan sebagai dekorator daripada antarmuka, tetapi kelayakan solusi itu benar-benar tergantung pada struktur objek Anda dan kompleksitas implementasi Anda.
sumber
MutableObject
memilikin
metode yang mengubah keadaan danm
metode yang mengembalikan keadaan,ImmutableDecorator
dapat terus mengekspos metode yang mengembalikan keadaan (m
), dan, tergantung pada lingkungan, menegaskan atau melempar pengecualian ketika salah satu metode yang bisa diubah dipanggil.Dalam C #, metode yang mengharapkan objek dari tipe "tidak dapat diubah" seharusnya tidak memiliki parameter tipe antarmuka karena antarmuka C # tidak dapat menentukan kontrak immutability. Oleh karena itu, pedoman yang Anda usulkan sendiri tidak ada artinya dalam C # karena Anda tidak dapat melakukannya sejak awal (dan versi bahasa yang akan datang tidak memungkinkan Anda melakukannya).
Pertanyaan Anda berasal dari kesalahpahaman halus artikel Eric Lippert tentang ketidakberdayaan. Eric tidak mendefinisikan antarmuka
IStack<T>
danIQueue<T>
untuk menentukan kontrak untuk tumpukan dan antrian yang tidak berubah. Mereka tidak melakukannya. Dia mendefinisikan mereka untuk kenyamanan. Antarmuka ini memungkinkannya untuk menentukan tipe berbeda untuk tumpukan kosong dan antrian. Kita dapat mengusulkan desain dan implementasi stack yang berbeda dengan menggunakan tipe tunggal tanpa memerlukan antarmuka atau tipe terpisah untuk mewakili stack kosong, tetapi kode yang dihasilkan tidak akan terlihat bersih dan akan sedikit kurang efisien.Sekarang mari kita berpegang pada desain Eric. Metode yang membutuhkan stack yang tidak dapat diubah harus memiliki parameter tipe
Stack<T>
daripada antarmuka umumIStack<T>
yang mewakili tipe data abstrak dari stack dalam arti umum. Tidak jelas bagaimana melakukan ini ketika menggunakan tumpukan Eric yang tidak bisa diubah dan dia tidak membahas ini di artikelnya, tapi itu mungkin. Masalahnya adalah dengan jenis tumpukan kosong. Anda dapat mengatasi masalah ini dengan memastikan bahwa Anda tidak pernah mendapatkan tumpukan kosong. Ini dapat dipastikan dengan mendorong nilai dummy sebagai nilai pertama pada tumpukan dan tidak pernah mengetuknya. Dengan cara ini, Anda dapat dengan aman melemparkan hasilPush
danPop
keStack<T>
.Memiliki
Stack<T>
alatIStack<T>
dapat bermanfaat. Anda dapat menentukan metode yang memerlukan tumpukan, tumpukan apa pun, belum tentu tumpukan yang tidak dapat diubah. Metode-metode ini dapat memiliki parameter tipeIStack<T>
. Ini memungkinkan Anda untuk melewati tumpukan yang tidak bisa diubah. Idealnya,IStack<T>
akan menjadi bagian dari perpustakaan standar itu sendiri. Di .NET, tidak adaIStack<T>
, tetapi ada antarmuka standar lain yang dapat diimplementasikan oleh stack immutable, membuat tipe ini lebih bermanfaat.Desain alternatif dan implementasi stack tidak berubah yang saya sebutkan sebelumnya menggunakan antarmuka yang disebut
IImmutableStack<T>
. Tentu saja, memasukkan "Immutable" di nama antarmuka tidak membuat setiap jenis yang mengimplementasikannya tidak dapat diubah. Namun, dalam antarmuka ini, kontrak imutabilitas hanyalah verbal. Pengembang yang baik harus menghargainya.Jika Anda sedang mengembangkan perpustakaan internal kecil, maka Anda dapat setuju dengan semua orang di tim untuk menghormati kontrak ini dan Anda dapat menggunakan
IImmutableStack<T>
sebagai jenis parameter. Jika tidak, Anda tidak boleh menggunakan tipe antarmuka.Saya ingin menambahkan, karena Anda menandai pertanyaan C #, bahwa dalam spesifikasi C #, tidak ada DTO dan POD. Oleh karena itu, membuangnya atau dengan tepat mendefinisikannya meningkatkan pertanyaan.
sumber