Apakah ada prinsip antarmuka "minta hanya apa yang Anda butuhkan"?

9

Saya telah tumbuh menggunakan prinsip untuk mendesain dan mengonsumsi antarmuka yang pada dasarnya mengatakan, "minta hanya apa yang Anda butuhkan."

Misalnya, jika saya memiliki banyak jenis yang dapat dihapus, saya akan membuat Deletableantarmuka:

interface Deletable {
   void delete();
}

Maka saya bisa menulis kelas generik:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

Di tempat lain dalam kode saya akan selalu meminta tanggung jawab sekecil mungkin untuk memenuhi kebutuhan kode klien. Jadi jika saya hanya perlu menghapus File, saya akan tetap meminta Deletable, bukan a File.

Apakah prinsip ini adalah pengetahuan umum dan sudah memiliki nama yang diterima? Apakah ini kontroversial? Apakah ini dibahas dalam buku teks?

glenviewjeff
sumber
1
Kopling longgar mungkin? Atau antarmuka yang sempit?
tdammers

Jawaban:

16

Saya percaya bahwa ini merujuk pada apa yang Robert Martin sebut sebagai Prinsip Segregasi Antarmuka . Antarmuka dipisahkan menjadi yang kecil dan ringkas sehingga konsumen (klien) hanya perlu tahu tentang metode yang menarik bagi mereka. Anda dapat memeriksa lebih lanjut tentang SOLID .

Vadim
sumber
4

Untuk memperluas jawaban Vadim yang sangat baik, saya akan menjawab pertanyaan "apakah ini kontroversial" dengan "tidak, tidak benar-benar".

Secara umum, pemisahan antarmuka adalah hal yang baik, dengan mengurangi jumlah keseluruhan "alasan untuk berubah" dari berbagai objek yang terlibat. Prinsip intinya adalah, ketika sebuah antarmuka dengan beberapa metode harus diubah, katakanlah untuk menambahkan parameter ke salah satu metode antarmuka, maka semua konsumen antarmuka setidaknya harus dikompilasi ulang, bahkan jika mereka tidak menggunakan metode yang diubah.. "Tapi itu hanya kompilasi ulang!", Saya mendengar Anda berkata; itu mungkin benar, tetapi perlu diingat bahwa biasanya, apa pun yang Anda kompilasi ulang harus didorong keluar sebagai bagian dari tambalan perangkat lunak, tidak peduli seberapa penting perubahan ke biner. Aturan-aturan ini awalnya dikonseptualisasikan kembali di awal 90-an, ketika rata-rata workstation desktop kurang kuat daripada ponsel di saku Anda, dial-up baud 14,4k sangat mencolok, dan "disket" 1,44MB "disket" adalah media utama yang dapat dilepas. Bahkan di era 3G / 4G saat ini, pengguna internet nirkabel sering memiliki paket data dengan batasan, jadi ketika merilis upgrade, semakin sedikit binari yang harus diunduh, semakin baik.

Namun, seperti semua ide bagus, pemisahan antarmuka dapat menjadi buruk jika tidak diterapkan dengan benar. Pertama, ada kemungkinan bahwa dengan memisahkan antarmuka sambil menjaga objek yang mengimplementasikan antarmuka tersebut (memenuhi dependensi) relatif tidak berubah, Anda mungkin berakhir dengan "Hydra", kerabat dari "God Object" anti-pola di mana semua-mengetahui, semua-kuat sifat objek disembunyikan dari ketergantungan oleh antarmuka yang sempit. Anda berakhir dengan monster berkepala banyak yang setidaknya sulit untuk mempertahankan seperti Obyek Dewa, ditambah overhead untuk mempertahankan semua antarmuka. Tidak ada banyak antarmuka yang tidak boleh Anda lewati, tetapi setiap antarmuka yang Anda implementasikan pada satu objek harus diawali dengan menjawab pertanyaan, "Apakah antarmuka ini berkontribusi pada objek '

Kedua, antarmuka per metode mungkin tidak diperlukan, terlepas dari apa yang mungkin dikatakan SRP kepada Anda. Anda mungkin berakhir dengan "kode ravioli"; begitu banyak potongan seukuran gigitan yang sulit dilacak untuk mencari tahu di mana sebenarnya hal-hal itu terjadi. Membagi antarmuka dengan dua metode juga tidak perlu jika semua pengguna saat ini membutuhkan kedua metode tersebut. Bahkan jika salah satu kelas dependen hanya membutuhkan salah satu dari dua metode, secara umum dapat diterima untuk tidak membagi antarmuka jika metodenya secara konseptual memiliki kohesi yang sangat tinggi (contoh yang baik adalah "metode antonim" yang merupakan pertentangan yang tepat satu sama lain).

Segregasi antarmuka harus didasarkan pada kelas yang bergantung pada antarmuka:

  • Jika hanya ada satu kelas yang tergantung pada antarmuka, jangan pisahkan. Jika kelas tidak menggunakan satu atau lebih metode antarmuka, dan itu adalah satu-satunya konsumen antarmuka, kemungkinan besar Anda seharusnya tidak mengekspos metode-metode itu di tempat pertama.

  • Jika ada lebih dari satu kelas yang bergantung pada antarmuka, dan semua tanggungan menggunakan semua metode antarmuka, jangan pisahkan; jika Anda harus mengubah antarmuka (untuk menambahkan metode atau mengubah tanda tangan), semua konsumen saat ini akan dipengaruhi oleh perubahan apakah Anda memisahkan atau tidak (meskipun jika Anda menambahkan metode yang setidaknya tidak perlu satu ketergantungan, pertimbangkan hati-hati jika perubahan itu seharusnya diimplementasikan sebagai antarmuka baru, mungkin mewarisi dari yang sudah ada).

  • Jika ada lebih dari satu kelas yang bergantung pada antarmuka, dan mereka tidak menggunakan semua metode yang sama, itu adalah kandidat untuk pemisahan. Lihatlah "koherensi" antarmuka; apakah semua metode memajukan satu tujuan pemrograman yang sangat spesifik? Jika Anda dapat mengidentifikasi lebih dari satu tujuan inti untuk antarmuka (dan implementornya), pertimbangkan untuk memisahkan antarmuka di sepanjang garis tersebut untuk membuat antarmuka yang lebih kecil dengan lebih sedikit "alasan untuk berubah".

KeithS
sumber
Perlu juga dicatat bahwa pemisahan antarmuka mungkin baik dan keren jika seseorang menggunakan bahasa OOP / sistem yang dapat memungkinkan kode untuk menentukan kombinasi antarmuka yang tepat, tetapi setidaknya dalam. NET mereka dapat menyebabkan beberapa sakit kepala yang parah, karena tidak ada yang layak cara menentukan koleksi "hal-hal yang menerapkan IFoo dan IBar, tetapi mungkin tidak memiliki kesamaan".
supercat
Parameter tipe umum dapat didefinisikan dengan kriteria termasuk penerapan beberapa antarmuka, tetapi Anda benar dalam ekspresi yang memerlukan tipe statis biasanya tidak dapat mendukung menentukan lebih dari satu. Jika ada kebutuhan bahwa tipe statis mengimplementasikan IFoo dan IBar, dan Anda mengontrol kedua antarmuka itu, mungkin ide yang baik untuk mengimplementasikan IBaz : IFoo, IBardan mengharuskan itu.
KeithS
Jika kode klien mungkin memerlukan sesuatu yang dapat digunakan sebagai IFoodan IBar, mendefinisikan komposit IFooBarmungkin merupakan ide yang baik, tetapi jika antarmuka terpecah dengan baik, mudah untuk akhirnya membutuhkan lusinan jenis antarmuka yang berbeda. Pertimbangkan koleksi fitur-fitur berikut yang mungkin dimiliki: Hitung, laporkan Hitungan, Baca elemen ke-n, Tulis elemen ke-n, Sisipkan sebelum elemen ke-n, Hapus elemen ke-n, Item baru (perbesar koleksi dan kembalikan indeks ruang baru), dan Tambah. Sembilan metode: ECRWIDNA. Saya mungkin bisa menggambarkan lusinan jenis yang secara alami akan mendukung banyak kombinasi yang berbeda.
supercat
Array, misalnya, akan mendukung ECRW. Arraylist akan mendukung ECRWIDNA. Daftar thread-safe mungkin mendukung ECRWNA [meskipun A umumnya hanya akan berguna untuk pra-mengisi daftar]. Pembungkus array read-only mungkin mendukung ECR. Antarmuka daftar kovarian dapat mendukung ECRD. Antarmuka non-generik dapat memberikan dukungan C atau CD jenis-aman. Jika Swap adalah opsi, beberapa jenis dapat mendukung CS tetapi tidak D (misalnya array) sementara yang lain akan mendukung CDS. Mencoba mendefinisikan tipe antarmuka yang berbeda untuk setiap kombinasi kemampuan yang diperlukan akan menjadi mimpi buruk.
supercat
Sekarang bayangkan seseorang ingin kemampuan untuk membungkus koleksi dengan objek yang dapat melakukan semua koleksi yang bisa dilakukan, tetapi mencatat setiap transaksi. Berapa bungkus yang dibutuhkan seseorang? Jika semua koleksi diwarisi dari antarmuka umum yang menyertakan properti untuk mengidentifikasi kemampuan mereka, satu bungkus akan cukup. Namun, jika semua antarmuka berbeda, orang akan membutuhkan puluhan.
supercat