Saya memiliki modul, katakan 'M', yang memiliki beberapa klien, katakan 'C1', 'C2', 'C3'. Saya ingin membagi namespace modul M, yaitu deklarasi API dan data yang dipaparkannya, ke dalam file header sedemikian rupa sehingga -
- untuk klien mana pun hanya data dan API yang diperlukannya yang terlihat; sisa namespace modul disembunyikan dari klien yaitu mematuhi prinsip Segregasi Antarmuka .
- deklarasi tidak diulang dalam beberapa file header yaitu tidak melanggar KERING .
- modul M tidak memiliki ketergantungan pada kliennya.
- seorang klien tidak terpengaruh oleh perubahan yang dibuat di bagian-bagian modul M yang tidak digunakan olehnya.
- klien yang ada tidak terpengaruh oleh penambahan (atau penghapusan) lebih banyak klien.
Saat ini saya berurusan dengan ini dengan membagi namespace modul tergantung pada kebutuhan kliennya. Sebagai contoh, pada gambar di bawah ini bagian-bagian berbeda dari namespace modul yang diperlukan oleh 3 kliennya ditampilkan. Persyaratan klien tumpang tindih. Namespace modul dibagi menjadi 4 file header terpisah - 'a', '1', '2' dan '3' .
Namun, ini melanggar beberapa persyaratan yang disebutkan di atas yaitu R3 dan R5. Persyaratan 3 dilanggar karena partisi ini tergantung pada sifat klien; juga pada penambahan klien baru, partisi ini berubah dan melanggar persyaratan 5. Seperti yang dapat dilihat di sisi kanan gambar di atas, dengan penambahan klien baru ruang modul sekarang dibagi menjadi 7 file header - 'a ',' b ',' c ',' 1 ',' 2 * ',' 3 * 'dan' 4 ' . File header yang dimaksudkan untuk 2 perubahan klien yang lebih lama, sehingga memicu rekondisi mereka.
Apakah ada cara untuk mencapai Segregasi Antarmuka dalam C dengan cara yang tidak dibuat-buat?
Jika ya, bagaimana Anda menangani contoh di atas?
Solusi hipotetis yang tidak realistis yang saya bayangkan adalah -
Modul ini memiliki 1 file header besar yang mencakup seluruh namespace-nya. File header ini dibagi menjadi beberapa bagian yang dapat dialamatkan dan subbagian seperti halaman Wikipedia. Setiap klien kemudian memiliki file header khusus yang dirancang untuk itu. File header khusus klien hanyalah daftar hyperlink ke bagian / subbagian file header lemak. Dan sistem build harus mengenali file header khusus klien sebagai 'dimodifikasi' jika ada bagian yang ditunjukkan di header Modul dimodifikasi.
sumber
struct
adalah apa yang Anda gunakan di C saat Anda menginginkan antarmuka. Memang, metode agak sulit. Anda mungkin menemukan ini menarik: cs.rit.edu/~ats/books/ooc.pdfstruct
danfunction pointers
.Jawaban:
Segregasi Antarmuka, secara umum, tidak boleh didasarkan pada persyaratan klien. Anda harus mengubah seluruh pendekatan untuk mencapainya. Saya akan mengatakan, memodulasi antarmuka dengan mengelompokkan fitur menjadi koheren kelompok yang . Itu adalah pengelompokan berdasarkan pada koherensi fitur itu sendiri, bukan persyaratan klien. Dalam hal ini, Anda akan memiliki satu set antarmuka, I1, I2, ... dll. Klien C1 dapat menggunakan I2 saja. Klien C2 dapat menggunakan I1 dan I5 dll. Perhatikan bahwa, jika klien menggunakan lebih dari satu Ii, bukan masalah. Jika Anda telah menguraikan antarmuka menjadi modul yang koheren, di situlah inti masalahnya.
Sekali lagi, ISP bukan berbasis klien. Ini adalah tentang penguraian antarmuka menjadi modul yang lebih kecil. Jika ini dilakukan dengan benar, itu juga akan memastikan bahwa klien terkena fitur sesedikit yang mereka butuhkan.
Dengan pendekatan ini, klien Anda dapat meningkat ke angka apa pun tetapi Anda tidak terpengaruh. Setiap klien akan menggunakan satu atau beberapa kombinasi antarmuka berdasarkan kebutuhan mereka. Apakah akan ada kasus bahwa klien, C, perlu menyertakan say I1 dan I3, tetapi tidak menggunakan semua fitur dari antarmuka ini? Ya, itu bukan masalah. Itu hanya menggunakan jumlah antarmuka paling sedikit.
sumber
The Antarmuka Pemisahan Prinsip mengatakan:
Ada beberapa pertanyaan yang belum terjawab di sini. Satu adalah:
Seberapa kecil?
Kamu bilang:
Saya menyebutnya pengetikan manual bebek . Anda membangun antarmuka yang mengekspos hanya apa yang dibutuhkan klien. Prinsip pemisahan antarmuka bukan hanya mengetik bebek manual.
Tetapi ISP juga bukan sekadar panggilan untuk antarmuka peran "koheren" yang dapat digunakan kembali. Tidak ada desain antarmuka peran yang "koheren" yang dapat dengan sempurna melindungi terhadap penambahan klien baru dengan kebutuhan perannya sendiri.
ISP adalah cara untuk mengisolasi klien dari dampak perubahan pada layanan. Itu dimaksudkan untuk membuat build berjalan lebih cepat saat Anda membuat perubahan. Tentu itu memiliki manfaat lain, seperti tidak melanggar klien, tapi itulah poin utamanya. Jika saya mengubah
count()
tanda tangan fungsi layanan , bagus jika klien yang tidak menggunakancount()
tidak perlu diedit dan dikompilasi ulang.Inilah MENGAPA saya peduli tentang Prinsip Segregasi Antarmuka. Itu bukan sesuatu yang saya anggap penting. Ini memecahkan masalah nyata.
Jadi cara itu harus diterapkan harus menyelesaikan masalah untuk Anda. Tidak ada cara menghafal otak mati untuk menerapkan ISP yang tidak dapat dikalahkan hanya dengan contoh yang tepat dari perubahan yang diperlukan. Anda seharusnya melihat bagaimana sistem berubah dan membuat pilihan yang akan membuat segalanya menjadi tenang. Mari menjelajahi opsi.
Pertama-tama tanyakan pada diri Anda: apakah membuat perubahan pada antarmuka layanan sulit sekarang? Jika tidak, pergilah ke luar dan mainkan sampai Anda tenang. Ini bukan latihan intelektual. Pastikan obatnya tidak lebih buruk dari penyakitnya.
Jika banyak klien menggunakan subset fungsi yang sama, itu berpendapat untuk antarmuka yang dapat digunakan kembali "koheren". Subset ini kemungkinan berfokus pada satu gagasan yang dapat kita pikirkan sebagai peran yang diberikan layanan kepada klien. Sangat menyenangkan ketika ini bekerja. Ini tidak selalu berhasil.
Jika banyak klien menggunakan himpunan fungsi yang berbeda, mungkin saja klien tersebut benar-benar menggunakan layanan melalui berbagai peran. Tidak apa-apa tapi itu membuat peran sulit dilihat. Temukan mereka dan cobalah untuk menggoda mereka. Itu dapat menempatkan kita kembali dalam kasus 1. Klien hanya menggunakan layanan melalui lebih dari satu antarmuka. Tolong jangan mulai casting layanan. Jika ada yang berarti melewati layanan ke klien lebih dari sekali. Itu bekerja tetapi itu membuat saya mempertanyakan apakah layanan ini bukan bola lumpur besar yang perlu dipecah.
Jika banyak klien menggunakan himpunan bagian yang berbeda tetapi Anda tidak melihat peran bahkan memungkinkan bahwa klien mungkin menggunakan lebih dari satu maka Anda tidak memiliki yang lebih baik daripada mengetik bebek untuk merancang antarmuka Anda. Cara mendesain antarmuka ini memastikan bahwa klien tidak terpapar pada satu fungsi pun yang tidak digunakan tetapi hampir menjamin bahwa menambahkan klien baru akan selalu melibatkan penambahan antarmuka baru yang sementara implementasi layanan tidak perlu tahu tentang hal itu antarmuka yang akan menggabungkan antarmuka peran. Kami hanya menukar satu rasa sakit dengan yang lain.
Jika banyak klien menggunakan himpunan bagian yang berbeda, tumpang tindih, klien baru diharapkan untuk menambahkan bahwa akan memerlukan himpunan himpunan bagian yang tidak dapat diprediksi, dan Anda tidak ingin memecah layanan kemudian mempertimbangkan solusi yang lebih fungsional. Karena dua opsi pertama tidak berfungsi dan Anda benar-benar berada di tempat yang buruk di mana tidak ada yang mengikuti pola dan lebih banyak perubahan datang maka pertimbangkan untuk menyediakan setiap fungsi antarmuka itu sendiri. Mengakhiri di sini bukan berarti ISP gagal. Jika ada yang gagal itu adalah paradigma berorientasi objek. Antarmuka metode tunggal mengikuti ISP secara ekstrim. Ini adalah pengetikan keyboard yang lumayan tetapi Anda mungkin menemukan ini tiba-tiba membuat antarmuka dapat digunakan kembali. Sekali lagi, pastikan tidak ada
Jadi ternyata mereka bisa menjadi sangat kecil.
Saya menganggap pertanyaan ini sebagai tantangan untuk menerapkan ISP dalam kasus yang paling ekstrem. Namun perlu diingat bahwa ekstrem sebaiknya dihindari. Dalam desain yang dipikirkan dengan baik yang menerapkan prinsip - prinsip PADAT lainnya masalah ini biasanya tidak terjadi atau masalah, hampir sebanyak.
Pertanyaan lain yang belum terjawab adalah:
Siapa yang memiliki antarmuka ini?
Berkali-kali saya melihat antarmuka yang dirancang dengan apa yang saya sebut mentalitas "perpustakaan". Kita semua bersalah atas pengkodean monyet-lihat-monyet-di mana Anda hanya melakukan sesuatu karena itulah yang Anda lakukan. Kami bersalah atas hal yang sama dengan antarmuka.
Ketika saya melihat sebuah antarmuka yang dirancang untuk kelas di perpustakaan saya dulu berpikir: oh, orang-orang ini adalah pro. Ini harus menjadi cara yang tepat untuk melakukan antarmuka. Apa yang saya gagal mengerti adalah bahwa batas perpustakaan memiliki kebutuhan dan masalah sendiri. Untuk satu hal, perpustakaan sama sekali tidak tahu tentang desain klien itu. Tidak semua batas sama. Dan kadang-kadang bahkan batas yang sama memiliki cara berbeda untuk melewatinya.
Berikut adalah dua cara sederhana untuk melihat desain antarmuka:
Antarmuka yang dimiliki layanan. Beberapa orang merancang setiap antarmuka untuk mengekspos semua layanan yang dapat dilakukan. Anda bahkan dapat menemukan opsi refactoring di IDE yang akan menulis antarmuka untuk Anda menggunakan kelas apa pun yang Anda berikan.
Antarmuka yang dimiliki klien. ISP sepertinya berpendapat bahwa ini benar dan layanan yang dimiliki salah. Anda harus memecah setiap antarmuka dengan kebutuhan klien dalam pikiran. Karena klien memiliki antarmuka, ia harus mendefinisikannya.
Jadi siapa yang benar?
Pertimbangkan plugin:
Siapa yang memiliki antarmuka di sini? Klien? Pelayanan?
Ternyata keduanya.
Warna di sini adalah lapisan. Lapisan merah (kanan) tidak seharusnya tahu apa-apa tentang lapisan hijau (kiri). Lapisan hijau dapat diubah atau diganti tanpa menyentuh lapisan merah. Dengan begitu setiap lapisan hijau bisa dicolokkan ke lapisan merah.
Saya suka mengetahui apa yang seharusnya diketahui tentang apa, dan apa yang tidak seharusnya diketahui. Bagi saya, "apa yang tahu tentang apa?", Adalah satu-satunya pertanyaan arsitektur yang paling penting.
Mari kita perjelas kosakata:
Klien adalah sesuatu yang digunakan.
Suatu layanan adalah sesuatu yang digunakan.
Interactor
kebetulan keduanya.ISP mengatakan memecah antarmuka untuk klien. Baik, mari terapkan itu di sini:
Presenter
(layanan) tidak boleh menentukanOutput Port <I>
antarmuka. Antarmuka harus dipersempit untuk apaInteractor
(di sini bertindak sebagai klien) kebutuhan. Itu berarti antarmuka TAHU tentangInteractor
dan, untuk mengikuti ISP, harus berubah dengannya. Dan ini baik-baik saja.Interactor
(di sini bertindak sebagai layanan) tidak boleh menentukanInput Port <I>
antarmuka. Antarmuka harus dipersempit dengan apa yangController
(klien) butuhkan. Itu berarti antarmuka TAHU tentangController
dan, untuk mengikuti ISP, harus berubah dengannya. Dan ini tidak baik.Yang kedua tidak baik karena lapisan merah tidak seharusnya tahu tentang lapisan hijau. Jadi apakah ISP salah? Yah agak. Tidak ada prinsip yang mutlak. Ini adalah kasus di mana orang-orang bodoh yang menyukai antarmuka untuk menunjukkan segala sesuatu yang dapat dilakukan layanan ternyata benar.
Setidaknya, mereka benar jika
Interactor
tidak melakukan apa pun selain dari kebutuhan use case ini. JikaInteractor
hal-hal untuk kasus penggunaan lain tidak ada alasan iniInput Port <I>
harus tahu tentang mereka. Tidak yakin mengapaInteractor
tidak bisa hanya fokus pada satu Use Case jadi ini bukan masalah, tetapi banyak hal terjadi.Tetapi
input port <I>
antarmuka tidak bisa menjadi budak bagiController
klien dan menjadikannya plugin yang benar. Ini adalah batas 'perpustakaan'. Toko pemrograman yang sama sekali berbeda bisa menulis lapisan hijau bertahun-tahun setelah lapisan merah diterbitkan.Jika Anda melewati batas 'perpustakaan' dan Anda merasa perlu menerapkan ISP walaupun Anda tidak memiliki antarmuka di sisi lain, Anda harus menemukan cara untuk mempersempit antarmuka tanpa mengubahnya.
Salah satu cara untuk melakukannya adalah adaptor. Letakkan di antara suka klien
Controler
danInput Port <I>
antarmuka. Adaptor menerimaInteractor
sebagaiInput Port <I>
dan mendelegasikan itu berfungsi untuk itu. Namun, itu hanya mengekspos apa yangController
dibutuhkan klien melalui antarmuka peran atau antarmuka yang dimiliki oleh lapisan hijau. Adaptor tidak mengikuti ISP sendiri tetapi memungkinkan kelas yang lebih kompleks inginController
menikmati ISP. Ini berguna jika ada lebih sedikit adapter daripada klien sepertiController
itu yang menggunakannya dan ketika Anda berada dalam situasi yang tidak biasa di mana Anda melewati batas perpustakaan dan, meskipun diterbitkan, perpustakaan tidak akan berhenti berubah. Melihat Anda, Firefox. Sekarang perubahan itu hanya merusak adaptor Anda.Jadi apa artinya ini? Itu berarti jujur Anda belum memberikan informasi yang cukup bagi saya untuk memberi tahu Anda apa yang harus Anda lakukan. Saya tidak tahu apakah tidak mengikuti ISP menyebabkan Anda mengalami masalah. Saya tidak tahu apakah mengikutinya tidak akan menyebabkan lebih banyak masalah bagi Anda.
Saya tahu Anda sedang mencari prinsip panduan sederhana. ISP mencoba menjadi seperti itu. Tapi banyak yang tidak terucapkan. Saya percaya akan hal itu. Ya, tolong jangan memaksa klien untuk bergantung pada metode yang tidak mereka gunakan, tanpa alasan yang bagus!
Jika Anda memiliki alasan yang bagus, seperti mendesain sesuatu untuk menerima plugin, maka waspadai masalah tidak mengikuti penyebab ISP (sulit untuk berubah tanpa melanggar klien), dan cara untuk mengurangi mereka (tetap
Interactor
atau setidaknyaInput Port <I>
fokus pada satu stabil) gunakan kasing).sumber
Jadi poin ini:
Menyerah bahwa Anda melanggar prinsip penting lainnya yaitu YAGNI. Saya akan peduli ketika saya memiliki ratusan klien. Memikirkan sesuatu di muka dan kemudian ternyata Anda tidak memiliki klien tambahan untuk kode ini melebihi tujuannya.
Kedua
Mengapa kode Anda tidak menggunakan DI, inversi ketergantungan, tidak ada, tidak ada di perpustakaan Anda harus tergantung pada sifat klien Anda.
Akhirnya sepertinya Anda memerlukan lapisan tambahan di bawah kode Anda untuk melayani kebutuhan akan hal-hal yang tumpang tindih (DI sehingga kode menghadap depan Anda hanya bergantung pada lapisan tambahan ini, dan klien Anda hanya bergantung pada antarmuka menghadap depan) dengan cara ini Anda mengalahkan KERING.
Ini Anda akan od itu nyata. Jadi Anda membuat barang yang sama yang Anda gunakan di lapisan modul di bawah modul lain. Dengan cara ini memiliki lapisan di bawah yang Anda raih:
Iya
Iya
Iya
Iya
sumber
Informasi yang sama seperti yang diberikan dalam deklarasi selalu diulang dalam definisi. Hanya cara bahasa ini bekerja. Juga, mengulangi deklarasi dalam beberapa file header tidak melanggar KERING . Ini adalah teknik yang agak umum digunakan (setidaknya di perpustakaan standar).
Mengulangi dokumentasi atau implementasinya akan melanggar KERING .
Saya tidak akan repot dengan ini kecuali kode klien tidak ditulis oleh saya.
sumber
Saya menafikan kebingungan saya. Namun contoh praktis Anda menarik solusi di kepala saya. Jika saya dapat mengutarakan kata-kata saya sendiri: semua partisi dalam modul
M
memiliki banyak hubungan eksklusif dengan banyak klien.Struktur Sampel
Mh
Mc
Dalam file Mc, Anda sebenarnya tidak harus menggunakan #ifdefs karena apa yang Anda masukkan dalam file .c tidak mempengaruhi file klien selama fungsi yang ditentukan oleh file klien digunakan.
C1.c
C2.c
C3.c
Sekali lagi, saya tidak yakin apakah ini yang Anda tanyakan. Jadi bawa dengan sebutir garam.
sumber
P1_init()
danP2_init()
?P1_init()
danP2_init()
tautan ke?_PREF_
dengan apa pun yang terakhir kali didefinisikan. Jadi_PREF_init()
akanP1_init()
karena pernyataan #define terakhir. Kemudian pernyataan define berikutnya akan menetapkan PREF sama dengan P2_, sehingga menghasilkanP2_init()
.