Pastikan bahwa setiap kelas hanya memiliki satu tanggung jawab, mengapa?

37

Menurut dokumentasi Microsoft, artikel prinsip Wikipedia SOLID, atau sebagian besar arsitek TI kita harus memastikan bahwa setiap kelas hanya memiliki satu tanggung jawab. Saya ingin tahu mengapa, karena jika semua orang tampaknya setuju dengan aturan ini, tidak ada yang tampaknya setuju tentang alasan aturan ini.

Beberapa mengutip pemeliharaan yang lebih baik, beberapa mengatakan itu memberikan pengujian yang mudah atau membuat kelas lebih kuat, atau keamanan. Apa yang benar dan apa arti sebenarnya? Mengapa ini membuat pemeliharaan lebih baik, pengujian lebih mudah atau kode lebih kuat?

Bastien Vandamme
sumber
1
Saya punya pertanyaan yang mungkin Anda temukan juga terkait: Apa tanggung jawab nyata sebuah kelas?
Pierre Arlaud
3
Tidak bisakah semua alasan tanggung jawab tunggal itu benar? Itu membuat perawatan lebih mudah. Itu membuat pengujian lebih mudah. Itu membuat kelas lebih kuat (secara umum).
Martin York
2
Untuk alasan yang sama Anda memiliki kelas sama sekali.
Davor Ždralo
2
Saya selalu memiliki masalah dengan pernyataan ini. Sangat sulit untuk mendefinisikan apa "tanggung jawab tunggal" itu. Tanggung jawab tunggal dapat berkisar di mana saja dari "verifikasi bahwa 1 + 1 = 2" hingga "memelihara log akurat dari semua uang yang dibayarkan ke dan dari akun perbankan perusahaan".
Dave Nay

Jawaban:

57

Modularitas. Bahasa apa pun yang layak akan memberi Anda cara untuk menyatukan potongan-potongan kode, tetapi tidak ada cara umum untuk menghapus kode yang besar tanpa programmer melakukan operasi pada sumbernya. Dengan memasukkan banyak tugas ke dalam satu konstruksi kode, Anda merampok diri sendiri dan orang lain dari kesempatan untuk menggabungkan bagian-bagiannya dengan cara lain, dan memperkenalkan dependensi yang tidak perlu yang dapat menyebabkan perubahan pada satu bagian mempengaruhi yang lain.

SRP sama berlaku untuk fungsi seperti halnya untuk kelas, tetapi bahasa OOP arus utama relatif buruk dalam menempelkan fungsi bersama.

Doval
sumber
12
+1 untuk menyebutkan pemrograman fungsional sebagai cara yang lebih aman untuk membuat abstraksi.
logc
> tetapi bahasa OOP umum relatif buruk dalam menempelkan fungsi bersama. <Mungkin karena bahasa OOP tidak mementingkan fungsi, tetapi pesan.
Jeff Hubbard
@ Jeff Jubff Saya pikir maksud Anda karena mereka mengambil setelah C / C ++. Tidak ada apa-apa tentang objek yang membuatnya saling eksklusif dengan fungsi. Sial, objek / antarmuka hanyalah catatan / struct fungsi. Berpura-pura fungsi tidak penting tidak dibenarkan baik dalam teori maupun praktik. Mereka tidak dapat dihindari - Anda akhirnya harus membuang "Runnables", "Factories", "Providers" dan "Actions" atau memanfaatkan apa yang disebut "pola" Strategi dan Metode Templat, dan berakhir dengan sekelompok boilerplate yang tidak perlu untuk mendapatkan pekerjaan yang sama dilakukan.
Doval
@Doval Tidak, saya benar-benar bermaksud bahwa OOP berkaitan dengan pesan. Itu bukan untuk mengatakan bahwa bahasa yang diberikan lebih baik daripada atau lebih buruk daripada bahasa pemrograman fungsional di perekatan fungsi bersama - hanya saja itu bukan perhatian utama bahasa .
Jeff Hubbard
Pada dasarnya, jika bahasa Anda adalah tentang membuat OOP lebih mudah / lebih baik / lebih cepat / lebih kuat, maka apakah fungsi berfungsi bersama dengan baik atau tidak bukanlah fokus Anda.
Jeff Hubbard
30

Perawatan yang lebih baik, pengujian yang mudah, perbaikan bug yang lebih cepat hanyalah hasil penerapan SRP yang sangat menyenangkan. Alasan utama (sebagaimana dikatakan Robert C. Matin) adalah:

Kelas harus memiliki satu, dan hanya satu, alasan untuk berubah.

Dengan kata lain, SRP memunculkan perubahan lokalitas .

SRP juga mempromosikan kode KERING. Selama kita memiliki kelas yang hanya memiliki satu tanggung jawab, kita dapat memilih untuk menggunakannya di mana pun kita inginkan. Jika kita memiliki kelas yang memiliki dua tanggung jawab, tetapi kita hanya perlu satu dari mereka dan yang kedua mengganggu, kita memiliki 2 pilihan:

  1. Salin-tempel kelas ke kelas lain dan mungkin bahkan membuat mutan multi-tanggung jawab lainnya (tingkatkan utang teknis sedikit).
  2. Bagilah kelas dan buat sebagaimana mestinya di tempat pertama, yang mungkin mahal karena penggunaan luas dari kelas asli.
Maciej Chałapuk
sumber
1
+1 Untuk menghubungkan SRP ke KERING.
Mahdi
21

Mudah membuat kode untuk memperbaiki masalah tertentu. Lebih rumit untuk membuat kode yang memperbaiki masalah itu sambil memungkinkan perubahan selanjutnya dilakukan dengan aman. SOLID menyediakan serangkaian praktik yang membuat kode lebih baik.

Yang mana yang benar: Mereka bertiga. Semua itu adalah manfaat menggunakan tanggung jawab tunggal dan alasan Anda harus menggunakannya.

Apa artinya:

  • Pemeliharaan yang lebih baik berarti lebih mudah untuk berubah dan tidak sering berubah. Karena ada lebih sedikit kode, dan kode itu difokuskan pada sesuatu yang spesifik, jika Anda perlu mengubah sesuatu yang tidak terkait dengan kelas, kelas tidak perlu diubah. Terlebih lagi, ketika Anda perlu mengubah kelas, selama Anda tidak perlu mengubah antarmuka publik, Anda hanya perlu khawatir tentang kelas itu dan tidak ada yang lain.
  • Pengujian mudah berarti lebih sedikit pengujian, lebih sedikit pengaturan. Tidak banyak bagian yang bergerak di kelas, jadi jumlah kemungkinan kegagalan di kelas lebih kecil, dan karena itu lebih sedikit kasus yang perlu Anda uji. Akan ada lebih sedikit bidang / anggota pribadi untuk disiapkan.
  • Karena kedua di atas Anda mendapatkan kelas yang berubah lebih sedikit dan gagal lebih sedikit, dan karenanya lebih kuat.

Cobalah membuat kode untuk beberapa saat mengikuti prinsip, dan kunjungi kembali kode itu nanti untuk melakukan beberapa perubahan. Anda akan melihat keuntungan besar yang diberikannya.

Anda melakukan hal yang sama untuk setiap kelas, dan berakhir dengan lebih banyak kelas, semua sesuai dengan SRP. Itu membuat struktur lebih kompleks dari sudut pandang koneksi, tetapi kesederhanaan setiap kelas membenarkannya.

Miyamoto Akira
sumber
Saya tidak berpikir poin yang dibuat di sini dibenarkan. Secara khusus, bagaimana Anda menghindari permainan zero-sum di mana penyederhanaan kelas menyebabkan kelas lain menjadi lebih rumit, tanpa efek bersih pada basis kode secara keseluruhan? Saya yakin jawaban Doval memang mengatasi masalah ini.
2
Anda melakukan hal yang sama untuk semua kelas. Anda diakhiri dengan lebih banyak kelas, semua sesuai dengan SRP. Itu membuat struktur lebih kompleks dari sudut pandang koneksi. Namun kesederhanaan di setiap kelas membenarkannya. Saya suka jawaban @ Doval.
Miyamoto Akira
Saya pikir Anda harus menambahkan itu ke jawaban Anda.
4

Berikut adalah argumen yang, dalam pandangan saya, mendukung klaim bahwa Prinsip Tanggung Jawab Tunggal adalah praktik yang baik. Saya juga menyediakan tautan ke literatur lebih lanjut, di mana Anda dapat membaca alasan yang lebih rinci - dan lebih fasih daripada milik saya:

  • Pemeliharaan yang lebih baik : idealnya, setiap kali fungsi sistem harus berubah, akan ada satu dan hanya satu kelas yang harus diubah. Pemetaan yang jelas antara kelas dan tanggung jawab berarti bahwa setiap pengembang yang terlibat dalam proyek dapat mengidentifikasi kelas ini. (Seperti yang dicatat oleh @ MaciejChałapuk, lihat Robert C. Martin, "Kode Bersih" .)

  • Pengujian yang lebih mudah : idealnya, kelas-kelas harus memiliki antarmuka publik seminimal mungkin, dan pengujian harus hanya membahas antarmuka publik ini. Jika Anda tidak dapat menguji dengan jelas, karena banyak bagian dari kelas Anda bersifat pribadi, maka ini adalah tanda yang jelas bahwa kelas Anda memiliki terlalu banyak tanggung jawab, dan bahwa Anda harus membaginya dalam subclass yang lebih kecil. Harap perhatikan bahwa ini berlaku juga untuk bahasa-bahasa di mana tidak ada anggota kelas 'publik' atau 'pribadi'; memiliki antarmuka publik yang kecil berarti sangat jelas untuk kode klien bagian mana dari kelas yang seharusnya digunakan. (Lihat Kent Beck, "Pengembangan Test-Driven" untuk lebih jelasnya.)

  • Robust code : kode Anda tidak akan gagal lebih atau kurang sering karena ditulis dengan baik; tetapi, seperti semua kode, tujuan utamanya bukan untuk berkomunikasi dengan mesin , tetapi dengan sesama pengembang (lihat Kent Beck, "Pola Implementasi" , Bab 1.) Basis kode yang jelas lebih mudah untuk dipikirkan, sehingga lebih sedikit bug akan diperkenalkan , dan lebih sedikit waktu akan berlalu antara menemukan bug dan memperbaikinya.

logc
sumber
Jika sesuatu harus berubah karena alasan bisnis percayalah dengan SRP Anda harus memodifikasi lebih dari satu kelas. Mengatakan bahwa jika suatu kelas berubah, itu hanya karena satu alasan tidak sama dengan jika ada perubahan, itu hanya akan berdampak pada satu kelas.
Bastien Vandamme
@ B413: Saya tidak bermaksud bahwa perubahan apa pun akan menyiratkan perubahan tunggal ke kelas tunggal. Banyak bagian dari sistem dapat membutuhkan perubahan untuk memenuhi perubahan bisnis tunggal. Tapi mungkin Anda ada benarnya dan saya harus menulis "kapan saja fungsi sistem harus berubah".
logc
3

Ada beberapa alasan, tetapi yang saya suka adalah pendekatan yang digunakan oleh banyak program UNIX awal: Lakukan satu hal dengan baik. Cukup sulit untuk melakukannya dengan satu hal, dan semakin sulit semakin banyak hal yang Anda coba lakukan.

Alasan lain adalah membatasi dan mengendalikan efek samping. Saya menyukai pembuka pintu pembuat kopi kombinasi saya. Sayangnya, kopi biasanya meluap ketika ada tamu. Saya lupa menutup pintu setelah saya membuat kopi beberapa hari yang lalu dan seseorang mencurinya.

Dari sudut pandang psikologis, Anda hanya dapat melacak beberapa hal sekaligus. Estimasi umum adalah tujuh plus atau minus dua. Jika suatu kelas melakukan banyak hal, Anda perlu melacak semuanya sekaligus. Ini mengurangi kemampuan Anda untuk melacak apa yang Anda lakukan. Jika suatu kelas melakukan tiga hal, dan Anda hanya menginginkan satu di antaranya, Anda dapat menggunakan kemampuan Anda untuk melacak hal-hal sebelum Anda benar-benar melakukan sesuatu dengan kelas tersebut.

Melakukan banyak hal meningkatkan kompleksitas kode. Di luar kode yang paling sederhana, meningkatkan kompleksitas meningkatkan kemungkinan bug. Dari sudut pandang ini Anda ingin kelas sesederhana mungkin.

Menguji kelas yang melakukan satu hal jauh lebih sederhana. Anda tidak perlu memverifikasi bahwa hal kedua yang dilakukan atau tidak terjadi di kelas untuk setiap tes. Anda juga tidak perlu memperbaiki kondisi yang rusak dan menguji ulang ketika salah satu dari tes ini gagal.

BillThor
sumber
2

Karena perangkat lunak adalah organik. Persyaratan berubah terus-menerus sehingga Anda harus memanipulasi komponen dengan sesedikit mungkin sakit kepala. Dengan tidak mengikuti prinsip-prinsip SOLID, Anda mungkin berakhir dengan basis kode yang ditetapkan dalam beton.

Bayangkan sebuah rumah dengan dinding beton bertulang. Apa yang terjadi ketika Anda mengeluarkan tembok ini tanpa dukungan apa pun? Rumah itu mungkin akan runtuh. Dalam perangkat lunak, kami tidak menginginkan ini, jadi kami menyusun aplikasi sedemikian rupa sehingga Anda dapat dengan mudah memindahkan / mengganti / memodifikasi komponen tanpa menyebabkan banyak kerusakan.

CodeART
sumber
1

Saya mengikuti pikiran: 1 Class = 1 Job.

Menggunakan Analogi Fisiologi: Motor (Sistem Saraf), Respirasi (Paru-paru), Pencernaan (Perut), Penciuman (Pengamatan), dll. Masing-masing akan memiliki subset pengontrol, tetapi masing-masing hanya memiliki 1 tanggung jawab, apakah itu untuk mengelola cara masing-masing subsistem mereka bekerja atau apakah mereka merupakan subsistem titik akhir dan hanya melakukan 1 tugas, seperti mengangkat jari atau menumbuhkan folikel rambut.

Jangan bingung fakta bahwa itu mungkin bertindak sebagai manajer, bukan pekerja. Beberapa pekerja akhirnya dipromosikan menjadi Manajer, ketika pekerjaan yang mereka lakukan menjadi terlalu rumit untuk satu proses untuk ditangani sendiri.

Bagian paling rumit dari apa yang saya alami, adalah mengetahui kapan harus menetapkan Kelas sebagai proses Operator, Supervisor, atau Manajer. Apa pun yang terjadi, Anda harus memperhatikan dan menunjukkan fungsinya untuk 1 tanggung jawab (Operator, Supervisor, atau Manajer).

Ketika Kelas / Objek melakukan lebih dari 1 peran tipe ini, Anda akan menemukan bahwa proses keseluruhan akan mulai mengalami masalah kinerja, atau memproses leher botol.

GoldBishop
sumber
Sekalipun implementasi pemrosesan oksigen atmosfer menjadi hemoglobin harus ditangani sebagai Lungkelas, saya berpendapat bahwa instance Personharus tetap Breathe, dan karenanya Personkelas harus mengandung cukup logika untuk, setidaknya, mendelegasikan tanggung jawab yang terkait dengan metode itu. Saya lebih lanjut menyarankan bahwa hutan entitas yang saling berhubungan yang hanya dapat diakses melalui pemilik bersama seringkali lebih mudah untuk dipertimbangkan daripada hutan yang memiliki beberapa titik akses independen.
supercat
Ya dalam hal itu Paru-paru adalah Manajer, meskipun Villi akan menjadi Pengawas, dan proses Pernafasan akan menjadi kelas Pekerja untuk mentransfer partikel Udara ke aliran Darah.
GoldBishop
@GoldBishop: Mungkin pandangan yang tepat adalah untuk mengatakan bahwa Personkelas memiliki tugasnya sebagai delegasi dari semua fungsi yang terkait dengan entitas "nyata-dunia-manusia-manusia" yang sangat rumit, termasuk asosiasi berbagai bagiannya . Mempunyai entitas melakukan 500 hal itu jelek, tetapi jika itu cara sistem dunia nyata dimodelkan bekerja, memiliki kelas yang mendelegasikan 500 fungsi sambil mempertahankan satu identitas mungkin lebih baik daripada harus melakukan segala sesuatu dengan potongan terpisah.
supercat
@supercat Atau Anda bisa mengkotak-kotakkan semuanya dan memiliki 1 Kelas dengan Supervisor yang diperlukan atas sub-proses. Dengan begitu, Anda bisa (secara teori) memiliki setiap proses terpisah dari kelas dasar Persontetapi masih melaporkan rantai pada keberhasilan / kegagalan tetapi tidak selalu mempengaruhi yang lain. 500 Fungsi (walaupun ditetapkan sebagai contoh, akan menjadi berlebihan dan tidak didukung) saya mencoba untuk menjaga tipe fungsionalitas yang diturunkan dan modular.
GoldBishop
@GoldBishop: Saya berharap ada beberapa cara untuk mendeklarasikan tipe "ephemeral", sehingga kode luar dapat memanggil anggota atau memberikannya sebagai parameter untuk metode tipe itu sendiri, tetapi tidak ada yang lain. Dari perspektif organisasi kode, pengelompokan kelas masuk akal, dan bahkan memiliki kode luar memanggil metode satu tingkat ke bawah (misalnya Fred.vision.lookAt(George)masuk akal, tetapi mengizinkan kode untuk mengatakan someEyes = Fred.vision; someTubes = Fred.digestiontampak menjengkelkan karena mengaburkan hubungan antara someEyesdan someTubes.
supercat
1

Terutama dengan prinsip penting seperti Tanggung Jawab Tunggal, saya pribadi berharap bahwa ada banyak alasan mengapa orang mengadopsi prinsip ini.

Beberapa alasan itu mungkin:

  • Pemeliharaan - SRP memastikan bahwa mengubah tanggung jawab dalam satu kelas tidak memengaruhi tanggung jawab lainnya, menjadikan pemeliharaan lebih mudah. Itu karena jika setiap kelas hanya memiliki satu tanggung jawab, perubahan yang dilakukan untuk satu tanggung jawab terisolasi dari tanggung jawab lain.
  • Pengujian - Jika suatu kelas memiliki satu tanggung jawab, itu membuatnya lebih mudah untuk mengetahui bagaimana menguji tanggung jawab ini. Jika kelas memiliki banyak tanggung jawab, Anda harus memastikan Anda menguji yang benar dan bahwa tes tidak terpengaruh oleh tanggung jawab lain yang dimiliki kelas.

Perhatikan juga bahwa SRP hadir dengan bundel prinsip SOLID. Mematuhi SRP dan mengabaikan sisanya sama buruknya dengan tidak melakukan SRP sejak awal. Jadi Anda tidak harus mengevaluasi SRP dengan sendirinya, tetapi dalam konteks semua prinsip SOLID.

Euforia
sumber
... test is not affected by other responsibilities the class has, bisakah Anda menjelaskan hal ini?
Mahdi
1

Cara terbaik untuk memahami pentingnya prinsip-prinsip ini adalah memiliki kebutuhan.

Ketika saya masih seorang programmer pemula, saya tidak terlalu memikirkan desain, bahkan saya tidak tahu ada pola desain. Ketika program saya berkembang, mengubah satu hal berarti mengubah banyak hal lainnya. Sulit untuk melacak bug, kodenya besar dan berulang. Tidak ada banyak hierarki objek, semuanya ada di mana-mana. Menambahkan sesuatu yang baru, atau menghapus sesuatu yang lama akan memunculkan kesalahan di bagian lain dari program. Sosok pergi.

Pada proyek-proyek kecil, itu mungkin tidak masalah, tetapi dalam proyek-proyek besar, hal-hal bisa sangat mengerikan. Kemudian ketika saya menemukan konsep desain patters, saya berkata pada diri sendiri, "oh yah, melakukan ini akan membuat segalanya jadi lebih mudah kalau begitu".

Anda benar-benar tidak dapat memahami pentingnya pola desain sampai kebutuhan muncul. Saya menghormati polanya karena dari pengalaman saya bisa mengatakan mereka membuat pemeliharaan kode mudah dan kode kuat.

Namun, sama seperti Anda, saya masih ragu tentang "pengujian mudah", karena saya belum memiliki kebutuhan untuk masuk ke unit testing.

harsimranb
sumber
Pola entah bagaimana terhubung ke SRP, tetapi SRP tidak diperlukan, ketika menerapkan pola desain. Mungkin untuk menggunakan pola dan mengabaikan SRP sepenuhnya. Kami menggunakan pola untuk memenuhi persyaratan non-fungsional, tetapi menggunakan pola tidak diperlukan dalam program apa pun. Prinsip-prinsip sangat penting dalam membuat pengembangan tidak terlalu menyakitkan dan (IMO) harus menjadi kebiasaan.
Maciej Chałapuk
Saya sepenuhnya setuju dengan Anda. SRP, sampai batas tertentu, menegakkan desain objek bersih dan hierarki. Ini adalah mentalitas yang baik bagi pengembang untuk masuk, karena itu membuat pengembang mulai berpikir dalam hal objek, dan bagaimana mereka seharusnya. Secara pribadi, itu memiliki dampak yang sangat baik pada perkembangan saya juga.
harsimranb
1

Jawabannya adalah, seperti yang orang lain tunjukkan, semuanya benar, dan mereka semua saling memasukkan satu sama lain, lebih mudah untuk diuji membuat pemeliharaan lebih mudah membuat kode lebih kuat membuat pemeliharaan lebih mudah dan sebagainya ...

Semua ini bermuara pada prinsip utama - kode harus sekecil dan melakukan sesedikit yang diperlukan untuk menyelesaikan pekerjaan. Ini berlaku untuk aplikasi, file, atau kelas sebanyak fungsi. Semakin banyak hal yang dilakukan oleh sepotong kode, semakin sulit untuk memahami, mempertahankan, memperluas, atau menguji.

Saya pikir itu bisa diringkas dalam satu kata: ruang lingkup. Perhatikan ruang lingkup artefak, semakin sedikit hal dalam ruang lingkup pada titik tertentu dalam suatu aplikasi, semakin baik.

Lingkup diperluas = lebih banyak kerumitan = lebih banyak cara untuk kesalahan terjadi.

jmoreno
sumber
1

Jika kelas terlalu besar, akan sulit untuk mempertahankan, menguji dan memahami, jawaban lain telah mencakup kehendak ini.

Dimungkinkan bagi suatu kelas untuk memiliki lebih dari satu tanggung jawab tanpa masalah, tetapi Anda segera mendapatkan masalah dengan kelas yang terlalu rumit.

Namun memiliki aturan sederhana "hanya satu tanggung jawab" hanya membuatnya lebih mudah untuk mengetahui kapan Anda membutuhkan kelas baru .

Namun mendefinisikan "tanggung jawab" itu sulit, itu tidak berarti "melakukan segala sesuatu yang menurut spesifikasi aplikasi", keterampilan sebenarnya adalah tahu bagaimana memecah masalah menjadi unit-unit kecil "tanggung jawab".

Ian
sumber