Pertanyaan Apakah Collections.unmodifiableXXX metode melanggar LSP?


Prinsip Pergantian Liskov adalah salah satu prinsip PADAT. Saya telah membaca prinsip ini beberapa kali sekarang dan telah mencoba memahaminya.

Inilah yang saya dapatkan dari itu,

Prinsip ini terkait dengan kontrak perilaku yang kuat di kalangan   hierarki kelas. Subtipe harus dapat diganti dengan   supertype tanpa melanggar kontrak.

Saya telah membaca beberapa lainnya artikel juga dan saya agak bingung memikirkan pertanyaan ini. Melakukan Collections.unmodifiableXXX() metode tidak melanggar LSP?

Kutipan dari artikel yang terhubung di atas:

Dengan kata lain, ketika menggunakan objek melalui antarmuka kelas dasar,   pengguna hanya mengetahui prasyarat dan postconditions dari basis   kelas. Demikian, objek yang diturunkan tidak boleh mengharapkan pengguna tersebut untuk patuh   prasyarat yang lebih kuat dari yang dibutuhkan oleh kelas dasar

Mengapa saya berpikir demikian?

Sebelum

class SomeClass{
      public List<Integer> list(){
           return new ArrayList<Integer>(); //this is dumb but works
      }
}

Setelah

class SomeClass{
     public List<Integer> list(){
           return Collections.unmodifiableList(new ArrayList<Integer>()); //change in implementation
     }
}

Saya tidak bisa mengubah implentasi SomeClass untuk mengembalikan daftar yang tidak dapat dimodifikasi di masa depan. Kompilasi akan bekerja tetapi jika klien entah bagaimana mencoba untuk mengubah List kembali maka itu akan gagal saat runtime.

Apakah ini mengapa Guava telah dibuat terpisah ImmutableXXX antarmuka untuk koleksi?

Bukankah ini pelanggaran langsung terhadap LSP atau saya benar-benar salah?


32
2018-02-26 19:06


asal


Jawaban:


LSP mengatakan bahwa setiap subclass harus mematuhi kontrak yang sama dengan superclass. Baik atau tidak ini adalah kasusnya Collections.unmodifiableXXX() dengan demikian tergantung pada bagaimana kontrak ini dibaca.

Benda-benda itu kembali Collections.unmodifiableXXX() melempar pengecualian jika seseorang mencoba memanggil metode modifikasi apa pun pada mereka. Misalnya, jika add() disebut, sebuah UnsupportedOperationException akan dilemparkan.

Apakah kontrak umum dari add()? Menurut Dokumentasi API ini:

Pastikan bahwa koleksi ini berisi elemen yang ditentukan (opsional   operasi). Mengembalikan nilai true jika koleksi ini berubah sebagai akibat dari   panggilan. (Mengembalikan salah jika koleksi ini tidak mengizinkan duplikat dan   sudah berisi elemen yang ditentukan.)

Jika ini adalah kontrak penuh, maka memang varian yang tidak dapat dimodifikasi tidak dapat digunakan di semua tempat di mana koleksi dapat digunakan. Namun, spesifikasi berlanjut dan juga mengatakan bahwa:

Jika koleksi menolak untuk menambahkan elemen tertentu karena alasan apa pun   selain itu sudah mengandung elemen, ia harus melempar   pengecualian (bukannya mengembalikan yang salah). Ini mempertahankan invarian   bahwa koleksi selalu berisi elemen yang ditentukan setelah ini   panggil kembali.

Ini secara eksplisit memungkinkan implementasi untuk memiliki kode yang tidak menambahkan argumen add ke koleksi tetapi menghasilkan pengecualian. Tentu saja ini termasuk kewajiban bagi klien dari koleksi yang mereka ambil itu (hukum) kemungkinan ke dalam akun.

Jadi subtyping perilaku (atau LSP) masih terpenuhi. Tapi ini menunjukkan bahwa jika seseorang berencana untuk memiliki perilaku yang berbeda dalam subclass yang juga harus diramalkan dalam spesifikasi kelas teratas.

Pertanyaan bagus, ngomong-ngomong.


30
2018-02-26 19:20



Ya, saya yakin Anda sudah benar. Pada dasarnya, untuk memenuhi LSP Anda harus dapat melakukan apa saja dengan subtipe yang dapat Anda lakukan dengan supertype. Ini juga mengapa masalah Ellipse / Circle muncul dengan LSP. Jika seorang Ellipse memiliki setEccentricity metode, dan Lingkaran adalah subkelas dari Ellipse, dan benda-benda yang seharusnya bisa berubah, tidak ada cara yang dapat melaksanakan Lingkaran setEccentricity metode. Jadi, ada sesuatu yang dapat Anda lakukan dengan Ellipse yang tidak dapat Anda lakukan dengan Lingkaran, sehingga LSP dilanggar. † Demikian pula, ada sesuatu yang dapat Anda lakukan dengan rutin List yang tidak bisa Anda lakukan dengan yang dibungkus oleh Collections.unmodifiableList, jadi itu pelanggaran LSP.

Masalahnya adalah bahwa ada sesuatu di sini yang kita inginkan (sebuah daftar read-only yang tidak dapat berubah, tidak dapat dimodifikasi) yang tidak ditangkap oleh sistem jenis. Di C # yang bisa Anda gunakan IEnumerable yang menangkap ide urutan Anda dapat mengulang dan membaca, tetapi tidak menulis. Tetapi di Jawa hanya ada List, yang sering digunakan untuk daftar yang bisa berubah, tetapi yang kadang-kadang kita suka gunakan untuk daftar yang tidak dapat diubah.

Sekarang, beberapa orang mungkin mengatakan bahwa Lingkaran dapat menerapkan setEccentricity dan hanya membuang pengecualian, dan juga daftar yang tidak dapat dimodifikasi (atau yang tidak berubah dari Guava) melempar pengecualian ketika Anda mencoba untuk memodifikasinya. Tapi itu tidak benar-benar berarti adalah Daftar dari sudut pandang LSP. Pertama-tama, setidaknya melanggar prinsip paling tidak mengejutkan. Jika pemanggil mendapat pengecualian yang tidak terduga ketika mencoba menambahkan item ke daftar, itu cukup mengejutkan. Dan jika kode panggilan perlu mengambil langkah-langkah untuk membedakan antara daftar yang dapat dimodifikasi dan yang tidak bisa (atau bentuk yang eksentrisitasnya dapat diatur, dan yang tidak bisa dilakukan), maka salah satu tidak benar-benar dapat disubstitusi untuk yang lain. .

Akan lebih baik jika sistem tipe Java memiliki tipe untuk urutan atau koleksi yang hanya memungkinkan iterasi, dan satu lagi yang memungkinkan modifikasi. Mungkin Iterable dapat digunakan untuk ini, tetapi saya menduga itu tidak memiliki beberapa fitur (seperti size()) yang benar-benar diinginkannya. Sayangnya, saya pikir ini adalah batasan dari Java collections API saat ini.

Beberapa orang telah mencatat bahwa dokumentasi untuk Collection memungkinkan implementasi untuk membuang pengecualian dari add metode. Saya mengira bahwa ini berarti bahwa Daftar yang tidak dapat dimodifikasi adalah mematuhi surat hukum ketika datang ke kontrak untuk add tapi saya pikir yang satu harus memeriksa kode seseorang dan melihat berapa banyak tempat di sana yang melindungi panggilan untuk bermutasi metode Daftar (add, addAll, remove, clear) dengan blok coba / tangkap sebelum menyatakan bahwa LSP tidak dilanggar. Mungkin tidak, tapi itu berarti semua kode yang memanggil List.add pada Daftar yang diterima sebagai parameter rusak.

Itu pasti akan mengatakan banyak.

(Argumen serupa dapat menunjukkan bahwa gagasan itu null adalah anggota dari setiap jenis juga merupakan pelanggaran Prinsip Pengganti Liskov.)

† Saya tahu bahwa ada cara lain untuk mengatasi masalah Ellipse / Lingkaran, seperti membuatnya tidak dapat diubah, atau menghapus metode setEccentricity. Saya berbicara di sini hanya tentang kasus yang paling umum, sebagai analogi.


10
2018-02-26 19:27



Saya tidak percaya itu pelanggaran karena kontrak (yaitu List antarmuka) mengatakan bahwa operasi mutasi bersifat opsional.


4
2018-02-26 19:11



Saya pikir Anda tidak mencampur hal-hal di sini.
Dari LSP:

Gagasan Liskov tentang subtipe perilaku mendefinisikan gagasan tentang   substitusi untuk benda-benda yang bisa berubah; yaitu, jika S adalah subtipe T,   maka objek tipe T dalam suatu program dapat diganti dengan objek   ketik S tanpa mengubah salah satu sifat yang diinginkan itu   program (misalnya, kebenaran).

LSP mengacu pada subclass.

Daftar adalah sebuah antarmuka bukan superclass. Ini menentukan daftar metode yang disediakan oleh kelas. Tetapi hubungan itu tidak digabungkan seperti dengan kelas induk. Fakta bahwa kelas A dan kelas B mengimplementasikan antarmuka yang sama, tidak menjamin apa pun tentang perilaku kelas-kelas ini. Satu implementasi dapat selalu mengembalikan nilai true dan yang lainnya melempar pengecualian atau selalu mengembalikan false atau apa pun tetapi keduanya menempel ke antarmuka saat mereka menerapkan metode antarmuka sehingga pemanggil dapat memanggil metode pada objek.


1
2018-02-26 23:22