Misalkan Anda memiliki dua antarmuka:
interface Readable {
public void read();
}
interface Writable {
public void write();
}
Dalam beberapa kasus objek implementasi hanya dapat mendukung salah satunya, tetapi dalam banyak kasus implementasi akan mendukung kedua antarmuka. Orang-orang yang menggunakan antarmuka harus melakukan sesuatu seperti:
// can't write to it without explicit casting
Readable myObject = new MyObject();
// can't read from it without explicit casting
Writable myObject = new MyObject();
// tight coupling to actual implementation
MyObject myObject = new MyObject();
Tidak satu pun dari opsi ini yang sangat nyaman, terlebih lagi ketika mempertimbangkan bahwa Anda menginginkan ini sebagai parameter metode.
Salah satu solusinya adalah dengan mendeklarasikan antarmuka pembungkus:
interface TheWholeShabam extends Readable, Writable {}
Tetapi ini memiliki satu masalah khusus: semua implementasi yang mendukung Readable dan Writable harus mengimplementasikan TheWholeShabam jika mereka ingin kompatibel dengan orang yang menggunakan antarmuka. Meskipun tidak menawarkan apa pun selain jaminan kehadiran kedua antarmuka.
Apakah ada solusi bersih untuk masalah ini atau haruskah saya menggunakan antarmuka pembungkus?
MEMPERBARUI
Sebenarnya seringkali diperlukan untuk memiliki objek yang dapat dibaca dan ditulis sehingga memisahkan kekhawatiran dalam argumen tidak selalu merupakan solusi bersih.
UPDATE2
(diekstraksi sebagai jawaban sehingga lebih mudah untuk mengomentari)
UPDATE3
Berhati-hatilah karena penggunaan utama untuk ini bukan stream (walaupun mereka juga harus didukung). Streaming membuat perbedaan yang sangat spesifik antara input dan output dan ada pemisahan tanggung jawab yang jelas. Sebaliknya, pikirkan sesuatu seperti bytebuffer di mana Anda memerlukan satu objek yang dapat Anda tulis dan baca, satu objek yang memiliki keadaan yang sangat spesifik yang melekat padanya. Objek-objek ini ada karena mereka sangat berguna untuk beberapa hal seperti asynchronous I / O, encodings, ...
UPDATE4
Salah satu hal pertama yang saya coba adalah sama dengan saran yang diberikan di bawah ini (periksa jawaban yang diterima) tetapi ternyata terlalu rapuh.
Misalkan Anda memiliki kelas yang harus mengembalikan tipe:
public <RW extends Readable & Writable> RW getItAll();
Jika Anda memanggil metode ini, RW generik ditentukan oleh variabel yang menerima objek, jadi Anda perlu cara untuk menggambarkan var ini.
MyObject myObject = someInstance.getItAll();
Ini akan berfungsi tetapi sekali lagi mengikatnya ke implementasi dan mungkin benar-benar membuang classcastexeption saat runtime (tergantung pada apa yang dikembalikan).
Selain itu jika Anda ingin variabel kelas dari tipe RW, Anda perlu mendefinisikan generik di tingkat kelas.
sumber
Jawaban:
Ya, Anda dapat mendeklarasikan parameter metode Anda sebagai tipe yang kurang spesifik yang meluas keduanya
Readable
danWritable
:Deklarasi metode terlihat mengerikan, tetapi menggunakannya lebih mudah daripada harus tahu tentang antarmuka terpadu.
sumber
process(Readable readThing, Writable writeThing)
dan jika Anda harus memintanya menggunakanprocess(foo, foo)
.<RW extends Readable&Writable>
?process
melakukan banyak hal yang berbeda dan melanggar prinsip tanggung jawab tunggal.Jika ada tempat di mana Anda membutuhkan
myObject
keduanyaReadable
danWritable
Anda dapat:Refactor tempat itu? Membaca dan menulis adalah dua hal yang berbeda. Jika suatu metode melakukan keduanya, mungkin itu tidak mengikuti prinsip tanggung jawab tunggal.
Pass
myObject
dua kali, sebagai aReadable
dan sebagaiWritable
(dua argumen). Apa peduli metode ini apakah itu objek yang sama atau tidak?sumber
StreamWriter
vs.StreamReader
(dan banyak lainnya)Tak satu pun dari jawaban saat ini membahas situasi ketika Anda tidak membutuhkan yang dapat dibaca atau ditulis tetapi keduanya . Anda perlu jaminan bahwa ketika menulis ke A, Anda dapat membaca data itu kembali dari A, bukan menulis ke A dan membaca dari B dan hanya berharap mereka sebenarnya objek yang sama. Usecases berlimpah, misalnya di mana-mana Anda akan menggunakan ByteBuffer.
Lagi pula, saya hampir menyelesaikan modul yang sedang saya kerjakan dan saat ini saya telah memilih antarmuka pembungkus:
Sekarang setidaknya Anda bisa melakukan:
Implementasi container saya sendiri (saat ini 3) semuanya mengimplementasikan Container sebagai lawan dari interface terpisah tetapi jika seseorang melupakan hal ini dalam implementasi, IOUtils menyediakan metode utilitas:
Saya tahu ini bukan solusi optimal tetapi masih merupakan jalan keluar terbaik saat ini karena Container masih merupakan usecase yang cukup besar.
sumber
Diberikan contoh MyObject generik, Anda akan selalu perlu tahu apakah itu mendukung membaca atau menulis. Jadi, Anda akan memiliki kode seperti:
Dalam kasus sederhana, saya tidak berpikir Anda bisa memperbaiki ini. Tetapi jika
readThisReadable
ingin menulis Readable ke file lain setelah membacanya, itu menjadi canggung.Jadi saya mungkin akan pergi dengan ini:
Mengambil ini sebagai parameter,
readThisReadable
sekarangreadThisWholeShabam
dapat menangani kelas yang mengimplementasikan TheWholeShabam, bukan hanya MyObject. Dan itu dapat menulisnya jika dapat ditulis dan tidak menulisnya jika tidak. (Kami punya "Polimorfisme" nyata.)Jadi set kode pertama menjadi:
Dan Anda dapat menyimpan baris di sini dengan membaca ThisWholeShebam () melakukan pemeriksaan keterbacaan.
Ini berarti bahwa mantan kita yang hanya dapat dibaca harus mengimplementasikan isWriteable () (return false ) dan write () (tidak melakukan apa-apa), tetapi sekarang ia dapat pergi ke semua jenis tempat yang tidak dapat dilalui sebelumnya dan semua kode yang menangani TheWholeShabam objek akan menghadapinya tanpa upaya lebih lanjut dari pihak kita.
Satu hal lagi: jika Anda bisa menangani panggilan untuk membaca () di kelas yang tidak membaca dan panggilan untuk menulis () di kelas yang tidak menulis tanpa merusak sesuatu, Anda dapat melewati isReadable () dan isWriteable () metode. Ini akan menjadi cara paling elegan untuk menanganinya - jika berhasil.
sumber