Dua definisi yang bertentangan dengan Prinsip Segregasi Antarmuka - mana yang benar?

14

Saat membaca artikel tentang ISP, tampaknya ada dua definisi yang bertentangan tentang ISP:

Menurut definisi pertama (lihat 1 , 2 , 3 ), ISP menyatakan bahwa kelas yang mengimplementasikan antarmuka tidak boleh dipaksa untuk mengimplementasikan fungsionalitas yang tidak mereka butuhkan. Dengan demikian, antarmuka yang gemukIFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

harus dibagi menjadi antarmuka yang lebih kecil ISmall_1danISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

karena dengan cara ini saya MyClasshanya dapat mengimplementasikan metode yang diperlukan ( D()dan C()), tanpa dipaksa juga menyediakan implementasi dummy untuk A(), B()dan C():

Tetapi menurut definisi kedua (lihat 1 , 2 , jawaban oleh Nazar Merza ), ISP menyatakan bahwa MyClientmemanggil metode MyServicetidak boleh menyadari metode MyServiceyang tidak diperlukan. Dengan kata lain, jika MyClienthanya membutuhkan fungsionalitas C()dan D(), maka alih-alih

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

kita harus memisahkan MyService'smetode menjadi antarmuka khusus klien :

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

Jadi dengan definisi sebelumnya, tujuan ISP adalah untuk " membuat kehidupan kelas yang mengimplementasikan antarmuka IFat lebih mudah ", sedangkan dengan yang terakhir tujuan ISP adalah untuk " membuat kehidupan klien yang memanggil metode MyService lebih mudah ".

Manakah dari dua definisi berbeda ISP yang benar?

@MARJAN VENEMA

1.

Jadi ketika Anda akan membagi IFat menjadi antarmuka yang lebih kecil, metode mana yang berakhir di mana antarmuka kecil IS harus diputuskan berdasarkan seberapa kohesifnya para anggota.

Meskipun masuk akal untuk menempatkan metode kohesif dalam antarmuka yang sama, saya pikir dengan pola ISP kebutuhan klien lebih diutamakan daripada "keterpaduan" antarmuka. Dengan kata lain, saya pikir dengan ISP kita harus menggumpal dalam antarmuka yang sama metode-metode yang dibutuhkan oleh klien tertentu, bahkan jika itu berarti meninggalkan antarmuka itu metode-metode yang seharusnya, demi keterpaduan, juga dimasukkan ke dalam antarmuka yang sama?

Jadi, jika ada banyak klien yang hanya perlu dihubungi CutGreens, tetapi tidak juga GrillMeat, maka untuk mematuhi pola ISP kita hanya harus memasukkan ke CutGreensdalam ICook, tetapi tidak juga GrillMeat, meskipun kedua metode ini sangat kohesif ?!

2.

Saya pikir bahwa kebingungan Anda berasal dari asumsi tersembunyi dalam definisi pertama: bahwa kelas-kelas pelaksana sudah mengikuti prinsip tanggung jawab tunggal.

Dengan "menerapkan kelas yang tidak mengikuti SRP" apakah Anda merujuk ke kelas yang menerapkan IFatatau ke kelas yang menerapkan ISmall_1/ ISmall_2? Saya menganggap Anda merujuk ke kelas yang menerapkan IFat? Jika demikian, mengapa Anda menganggap mereka belum mengikuti SRP?

Terima kasih

EdvRusj
sumber
4
Mengapa tidak ada definisi berganda yang keduanya dilayani oleh prinsip yang sama?
Bobson
5
Definisi-definisi ini tidak saling bertentangan.
Mike Partridge
1
Tidak tentu saja kebutuhan klien tidak didahulukan dari keterpaduan suatu antarmuka. Anda dapat mengambil "aturan" ini jalan jauh dan berakhir dengan antarmuka metode tunggal di semua tempat yang sama sekali tidak masuk akal. Hentikan mengikuti aturan dan mulailah memikirkan tujuan pembuatan aturan ini. Dengan "kelas tidak mengikuti SRP" Saya tidak berbicara tentang kelas khusus dalam contoh Anda atau bahwa mereka belum mengikuti SRP. Baca lagi. Definisi pertama hanya menyebabkan pemisahan antarmuka jika antarmuka tidak mengikuti ISP dan kelas mengikuti SRP.
Marjan Venema
2
Definisi kedua tidak peduli dengan pelaksana. Ini mendefinisikan antarmuka dari perspektif penelepon dan tidak membuat asumsi tentang apakah pelaksana sudah ada atau tidak. Mungkin mengasumsikan bahwa ketika Anda mengikuti ISP dan datang untuk mengimplementasikan antarmuka tersebut, Anda tentu saja akan mengikuti SRP saat membuatnya.
Marjan Venema
2
Bagaimana Anda tahu sebelumnya apa yang akan ada klien dan metode apa yang mereka butuhkan? Kamu tidak bisa Apa yang dapat Anda ketahui sebelumnya adalah seberapa kohesifnya antarmuka Anda.
Tulains Córdova

Jawaban:

6

Keduanya benar

Cara saya membacanya, tujuan ISP (Interface Segregation Principle) adalah untuk menjaga antarmuka tetap kecil dan fokus: semua anggota antarmuka harus memiliki kohesi yang sangat tinggi. Kedua definisi tersebut dimaksudkan untuk menghindari antarmuka "jack-of-all-trade-master-of-none".

Segregasi antarmuka dan SRP (Prinsip Tanggung Jawab Tunggal) memiliki tujuan yang sama: memastikan komponen perangkat lunak yang kecil dan sangat kohesif. Mereka saling melengkapi. Pemisahan antarmuka memastikan bahwa antarmuka kecil, fokus, dan sangat kohesif. Mengikuti prinsip tanggung jawab tunggal memastikan bahwa kelas kecil, fokus dan sangat kohesif.

Definisi pertama yang Anda sebutkan berfokus pada pelaksana, yang kedua pada klien. Yang bertentangan dengan @ user61852, saya menganggapnya sebagai pengguna / penelepon antarmuka, bukan pelaksana.

Saya pikir bahwa kebingungan Anda berasal dari asumsi tersembunyi dalam definisi pertama: bahwa kelas-kelas pelaksana sudah mengikuti prinsip tanggung jawab tunggal.

Bagi saya definisi kedua, dengan klien sebagai penelepon antarmuka, adalah cara yang lebih baik untuk mencapai tujuan yang dimaksud.

Memisahkan

Dalam pertanyaan Anda, Anda menyatakan:

karena dengan cara ini MyClass saya hanya dapat mengimplementasikan metode yang diperlukan (D () dan C ()), tanpa dipaksa juga memberikan implementasi dummy untuk A (), B () dan C ():

Tapi itu membalikkan dunia.

  • Kelas yang mengimplementasikan antarmuka tidak menentukan apa yang dibutuhkan dalam antarmuka yang diimplementasikan.
  • Antarmuka menentukan metode apa yang harus disediakan oleh kelas pelaksana.
  • Penelepon antarmuka benar-benar adalah orang-orang yang menentukan fungsi apa yang mereka butuhkan untuk menyediakan antarmuka bagi mereka dan dengan demikian apa yang harus disediakan oleh pelaksana.

Jadi ketika Anda akan dipisah IFatmenjadi antarmuka yang lebih kecil, metode mana yang berakhir di mana ISmallantarmuka harus diputuskan berdasarkan seberapa kohesifnya anggota.

Pertimbangkan antarmuka ini:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

Metode apa yang akan Anda masukkan ICookdan mengapa? Apakah Anda CleanSinkbergabung GrillMeathanya karena Anda kebetulan memiliki kelas yang hanya melakukan itu dan beberapa hal lain tetapi tidak seperti metode lainnya? Atau apakah Anda akan membaginya menjadi dua antarmuka yang lebih kohesif, seperti:

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

Catatan deklarasi antarmuka

Definisi antarmuka sebaiknya berada pada unitnya sendiri, tetapi jika benar-benar perlu untuk hidup dengan pemanggil atau pelaksana, itu harus benar-benar dengan pemanggil. Kalau tidak, penelepon mendapat ketergantungan langsung pada implementer yang mengalahkan tujuan antarmuka sama sekali. Lihat juga: Mendeklarasikan antarmuka dalam file yang sama dengan kelas dasar, apakah ini praktik yang baik? pada Programmer dan Mengapa kita harus menempatkan antarmuka dengan kelas yang menggunakannya daripada yang mengimplementasikannya? di StackOverflow.

Marjan Venema
sumber
1
Bisakah Anda melihat pembaruan yang saya buat?
EdvRusj
"pemanggil mendapat ketergantungan langsung pada implementer " ... hanya jika Anda melanggar DIP (prinsip invensi ketergantungan), jika variabel internal pemanggil, parameter, nilai balik, dll adalah tipe ICookbukan tipe SomeCookImplementor, seperti mandat DIP, maka itu tidak harus bergantung pada SomeCookImplementor.
Tulains Córdova
@ user61852: Jika deklarasi antarmuka dan implementer berada di unit yang sama, saya segera mendapatkan ketergantungan pada implementer itu. Tidak harus pada saat dijalankan, tetapi paling pasti pada tingkat proyek, hanya dengan fakta bahwa itu ada. Proyek tidak lagi dapat dikompilasi tanpa itu atau apa pun yang digunakannya. Juga, Injeksi Ketergantungan tidak sama dengan Prinsip Pembalikan Ketergantungan. Anda mungkin tertarik dengan DIP di alam bebas
Marjan Venema
Saya menggunakan kembali contoh kode Anda dalam pertanyaan ini programmer.stackexchange.com/a/271142/61852 , memperbaikinya setelah sudah diterima. Saya memberi Anda kredit karena untuk contoh.
Tulains Córdova
Cool @ user61852 :) (dan terima kasih atas kreditnya)
Marjan Venema
14

Anda mengacaukan kata "klien" seperti yang digunakan dalam dokumen Gang of Four dengan "klien" seperti pada konsumen layanan.

"Klien", sebagaimana dimaksud oleh definisi Gang of Four, adalah kelas yang mengimplementasikan antarmuka. Jika kelas A mengimplementasikan antarmuka B, maka mereka mengatakan A adalah klien B. Jika tidak, frasa "klien tidak boleh dipaksa untuk mengimplementasikan antarmuka yang tidak mereka gunakan" tidak akan masuk akal karena "klien" (seperti pada konsumen) tidak dapat mengimplementasikan apa pun. Ungkapan itu hanya masuk akal ketika Anda melihat "klien" sebagai "implementor".

Jika "klien" berarti kelas yang "mengkonsumsi" (panggilan) metode kelas lain yang mengimplementasikan antarmuka besar, maka dengan memanggil dua metode yang Anda pedulikan dan mengabaikan sisanya, akan cukup untuk membuat Anda dipisahkan dari sisa metode yang tidak Anda gunakan.

Semangat prinsip ini adalah menghindari "klien" (kelas yang mengimplementasikan antarmuka) harus menerapkan metode dummy untuk mematuhi seluruh antarmuka ketika hanya peduli tentang serangkaian metode yang terkait.

Juga bertujuan untuk memiliki jumlah kopling yang lebih sedikit sehingga perubahan yang dilakukan di satu tempat menyebabkan dampak yang lebih kecil. Dengan memisahkan antarmuka Anda mengurangi kopling.

Masalah itu muncul ketika antarmuka melakukan terlalu banyak dan memiliki metode yang harus dibagi dalam beberapa antarmuka, bukan hanya satu.

Kedua contoh kode Anda OK . Hanya saja dalam yang kedua Anda menganggap "klien" berarti "kelas yang mengkonsumsi / memanggil layanan / metode yang ditawarkan oleh kelas lain".

Saya tidak menemukan kontradiksi dalam konsep yang dijelaskan dalam tiga tautan yang Anda berikan.

Tetap jelas bahwa "klien" adalah implementor , dalam pembicaraan SOLID.

Tulains Córdova
sumber
Tetapi menurut @pdr, sementara contoh kode di semua tautan mematuhi ISP, definisi ISP lebih tentang "mengisolasi klien (kelas yang memanggil metode kelas lain) dari mengetahui lebih banyak tentang layanan" daripada tentang " pencegahan dari klien (implementor) dipaksa untuk mengimplementasikan antarmuka yang tidak mereka gunakan. "
EdvRusj
1
@EdvRusj Jawaban saya didasarkan pada dokumen di situs Object Mentor (perusahaan Bob Martin), ditulis oleh Martin sendiri ketika ia berada di Gang Empat yang terkenal. Seperti yang Anda ketahui, Gnag of Four adalah sekelompok insinyur perangkat lunak, termasuk Martin, yang menciptakan akronim SOLID, mengidentifikasi dan mendokumentasikan prinsip-prinsip tersebut. docs.google.com/a/cleancoder.com/file/d/…
Tulains Córdova
Jadi Anda tidak setuju dengan @pdr dan karenanya Anda menemukan definisi pertama ISP (lihat posting asli saya) lebih menyenangkan?
EdvRusj
@ EddRusj Saya pikir keduanya benar. Tetapi yang kedua menambahkan kebingungan yang tidak perlu dengan menggunakan metafora klien / server. Jika saya harus memilih satu, saya akan pergi dengan Gang of Four resmi, yang merupakan yang pertama. Tetapi yang penting adalah untuk mengurangi ketergantungan dan ketergantungan yang tidak perlu yang merupakan semangat prinsip SOLID. Tidak masalah yang mana yang benar. Yang penting adalah Anda harus memisahkan antarmuka menurut bahaviors. Itu saja. Namun ketika ragu, langsung saja ke sumber aslinya.
Tulains Córdova
3
Saya sangat tidak setuju dengan pernyataan Anda bahwa "klien" adalah pelaksana dalam pembicaraan SOLID. Untuk satu itu adalah omong kosong linguistik untuk memanggil penyedia (implementer) klien dari apa yang ia sediakan (implementasi). Saya juga belum melihat artikel tentang SOLID yang mencoba menyampaikan ini, tetapi saya mungkin melewatkannya. Yang paling penting meskipun itu mengatur pelaksana antarmuka sebagai orang yang memutuskan apa yang harus di antarmuka. Dan itu tidak masuk akal bagi saya. Penelepon / pengguna antarmuka menentukan apa yang mereka butuhkan dari sebuah antarmuka dan pelaksana (jamak) antarmuka itu terikat untuk menyediakannya.
Marjan Venema
5

ISP adalah tentang mengisolasi klien dari mengetahui lebih banyak tentang layanan daripada yang perlu diketahui (melindunginya terhadap perubahan yang tidak terkait, misalnya). Definisi kedua Anda benar. Untuk bacaan saya, hanya satu dari tiga artikel itu yang menyatakan sebaliknya ( yang pertama ) dan itu benar-benar salah. (Sunting: Tidak, tidak salah, hanya menyesatkan.)

Definisi pertama jauh lebih erat terkait dengan LSP.

pdr
sumber
3
Di ISP, klien tidak boleh dipaksa untuk MENGKONSUMSI komponen antarmuka yang tidak mereka gunakan. Dalam LSP, LAYANAN tidak boleh dipaksa untuk menerapkan metode D karena kode panggilan memerlukan metode A. Mereka tidak bertentangan, mereka saling melengkapi.
pdr
2
@EdvRusj, objek yang mengimplementasikan InterfaceA yang dipanggil ClientA mungkin sebenarnya adalah objek yang sama persis yang mengimplementasikan InterfaceB yang dibutuhkan oleh Klien B. Dalam kasus yang jarang terjadi di mana klien yang sama perlu melihat objek yang sama seperti Kelas yang berbeda, kode tidak akan biasanya "menyentuh". Anda akan melihatnya sebagai A untuk satu tujuan dan B untuk tujuan lainnya.
Amy Blankenship
1
@EdvRusj: Mungkin membantu jika Anda memikirkan kembali definisi antarmuka Anda di sini. Ini tidak selalu merupakan antarmuka dalam istilah C # / Java. Anda bisa memiliki layanan yang kompleks dengan sejumlah kelas sederhana yang melilitnya, sehingga klien A menggunakan kelas pembungkus AX untuk "antarmuka" dengan layanan X. Dengan demikian, ketika Anda mengubah X dengan cara yang mempengaruhi A dan AX, Anda tidak terpaksa mempengaruhi BX dan B.
pdr
1
@EdvRusj: Akan lebih akurat untuk mengatakan bahwa A dan B tidak peduli jika mereka berdua memanggil X atau satu memanggil Y dan yang lainnya memanggil Z. ITULAH adalah titik mendasar dari ISP. Jadi Anda dapat memilih implementasi mana yang Anda pilih, dan dengan mudah mengubah pikiran Anda nanti. ISP tidak mendukung satu rute atau yang lain, tetapi LSP dan SRP mungkin.
pdr
1
@EdvRusj Tidak, klien A akan dapat mengganti Layanan X dengan Layanan y, yang keduanya akan mengimplementasikan antarmuka AX. X dan / atau Y dapat mengimplementasikan Antarmuka lainnya, tetapi ketika Klien memanggil mereka dengan AX, mereka tidak peduli dengan Antarmuka lainnya.
Amy Blankenship