Prinsip Tanggung Jawab Tunggal menyatakan bahwa kelas harus melakukan satu dan hanya satu hal. Beberapa kasus dipotong cukup jelas. Namun, yang lain sulit karena apa yang tampak seperti "satu hal" ketika dilihat pada tingkat abstraksi tertentu mungkin banyak hal bila dilihat pada tingkat yang lebih rendah. Saya juga takut bahwa jika Prinsip Tanggung Jawab Tunggal dihormati pada tingkat yang lebih rendah, kode ravioli verbose yang dipisahkan secara berlebihan, di mana lebih banyak baris dihabiskan untuk menciptakan kelas-kelas kecil untuk segala sesuatu dan menyalurkan informasi di sekitar daripada benar-benar menyelesaikan masalah yang dihadapi, dapat dihasilkan.
Bagaimana Anda menggambarkan apa arti "satu hal"? Apa saja tanda-tanda konkret bahwa kelas benar-benar melakukan lebih dari "satu hal"?
sumber
Jawaban:
Saya sangat suka cara Robert C. Martin (Paman Bob) menyatakan kembali Prinsip Tanggung Jawab Tunggal (terkait dengan PDF) :
Ini agak berbeda dari definisi tradisional "hanya melakukan satu hal", dan saya suka ini karena memaksa Anda untuk mengubah cara Anda berpikir tentang kelas Anda. Alih-alih memikirkan "apakah ini melakukan satu hal?", Anda malah berpikir tentang apa yang bisa berubah dan bagaimana perubahan itu memengaruhi kelas Anda. Jadi misalnya, jika perubahan basis data apakah kelas Anda perlu berubah? Bagaimana jika perangkat keluaran berubah (misalnya layar, atau perangkat seluler, atau printer)? Jika kelas Anda perlu diubah karena perubahan dari banyak arah lain, maka itu pertanda bahwa kelas Anda memiliki terlalu banyak tanggung jawab.
Dalam artikel terkait, Paman Bob menyimpulkan:
sumber
Saya terus bertanya pada diri sendiri masalah apa yang SRP coba selesaikan? Kapan SRP membantu saya? Inilah yang saya pikirkan:
Anda harus memperbaiki tanggung jawab / fungsionalitas di luar kelas ketika:
1) Anda telah menduplikasi fungsi (KERING)
2) Anda menemukan bahwa kode Anda memerlukan abstraksi tingkat lain untuk membantu Anda memahaminya (KISS)
3) Anda menemukan bahwa bagian-bagian fungsionalitas dipahami oleh para ahli domain Anda sebagai bagian dari komponen yang berbeda (Bahasa Di mana-mana)
Anda TIDAK HARUS menolak tanggung jawab di luar kelas ketika:
1) Tidak ada fungsi duplikat.
2) Fungsionalitas tidak masuk akal di luar konteks kelas Anda. Dengan kata lain, kelas Anda menyediakan konteks di mana lebih mudah untuk memahami fungsionalitasnya.
3) Pakar domain Anda tidak memiliki konsep tanggung jawab itu.
Terpikir oleh saya bahwa jika SRP diterapkan secara luas, kami menukar satu jenis kompleksitas (mencoba membuat head atau tail of class dengan terlalu banyak terjadi di dalam) dengan jenis lain (mencoba untuk menjaga semua kolaborator / level abstraksi langsung untuk mengetahui apa yang sebenarnya dilakukan kelas-kelas ini).
Jika ragu, tinggalkan saja! Anda selalu dapat refactor nanti, ketika ada kasus yang jelas untuk itu.
Bagaimana menurut anda?
sumber
Saya tidak tahu apakah ada skala objektif untuk itu, tetapi apa yang akan memberikan ini akan menjadi metode - bukan jumlah mereka tetapi berbagai fungsi mereka. Saya setuju Anda bisa mengambil dekomposisi terlalu jauh tetapi saya tidak akan mengikuti aturan yang keras dan cepat tentang hal itu.
sumber
Jawabannya ada pada definisi
Apa yang Anda definisikan sebagai tanggung jawab, pada akhirnya memberi Anda batasan.
Contoh:
Saya memiliki komponen yang memiliki tanggung jawab untuk menampilkan faktur -> dalam hal ini, jika saya mulai menambahkan sesuatu lebih banyak maka saya melanggar prinsipnya.
Jika di sisi lain jika saya mengatakan bertanggung jawab menangani faktur -> menambahkan beberapa fungsi yang lebih kecil (mis. Mencetak Faktur , memperbarui Faktur ) semuanya berada dalam batas itu.
Namun jika modul itu mulai menangani fungsi apa pun di luar faktur , maka itu akan berada di luar batas itu.
sumber
Saya selalu melihatnya di dua level:
Jadi sesuatu seperti objek domain bernama Dog:
Dog
adalah kelas saya tetapi Anjing dapat melakukan banyak hal! Saya mungkin memiliki metode sepertiwalk()
,run()
danbite(DotNetDeveloper spawnOfBill)
(maaf tidak bisa menolak; p).Jika
Dog
menjadi sulit maka saya akan berpikir tentang bagaimana kelompok metode tersebut dapat dimodelkan bersama di kelas lain, sepertiMovement
kelas, yang bisa berisi metode sayawalk()
danrun()
.Tidak ada aturan yang keras dan cepat, desain OO Anda akan berkembang seiring waktu. Saya mencoba untuk menggunakan antarmuka yang jelas / API publik serta metode sederhana yang melakukan satu hal dan satu hal dengan baik.
sumber
Saya melihatnya lebih sepanjang garis kelas seharusnya hanya mewakili satu hal. Untuk contoh @ Karianna yang sesuai, saya memiliki
Dog
kelas saya , yang memiliki metode untukwalk()
,run()
danbark()
. Saya tidak akan menambahkan metode untukmeaow()
,squeak()
,slither()
ataufly()
karena mereka tidak hal yang anjing lakukan. Mereka adalah hal-hal yang dilakukan hewan lain, dan hewan-hewan lain itu akan memiliki kelas sendiri untuk mewakili mereka.(BTW, jika anjing Anda terbang, maka Anda mungkin harus berhenti melemparkannya ke luar jendela).
sumber
SeesFood
sebagai karakteristik dariDogEyes
,Bark
sebagai sesuatu yang dilakukan oleh aDogVoice
, danEat
sebagai sesuatu yang dilakukan oleh aDogMouth
, maka logika sukaif (dog.SeesFood) dog.Eat(); else dog.Bark();
akan menjadiif (eyes.SeesFood) mouth.Eat(); else voice.Bark();
, kehilangan rasa identitas bahwa mata, mulut, dan suara semuanya terhubung ke satu kesatuan.Dog
kelas, maka itu mungkinDog
terkait. Jika tidak, maka Anda mungkin akan berakhir dengan sesuatu sepertimyDog.Eyes.SeesFood
bukan hanyaeyes.SeesFood
. Kemungkinan lain adalah yangDog
mengeksposISee
antarmuka yang menuntutDog.Eyes
properti danSeesFood
metode.Eye
kelas, tetapi seekor anjing harus "melihat" menggunakan matanya, bukan hanya memiliki mata yang bisa melihat. Seekor anjing bukan mata, tetapi juga bukan hanya pemegang mata. Ini adalah "hal yang [setidaknya bisa] dilihat", dan harus dideskripsikan sebagai antarmuka seperti itu. Bahkan seekor anjing buta dapat ditanyai apakah ia melihat makanan; itu tidak akan sangat berguna, karena anjing akan selalu berkata "tidak", tetapi tidak ada salahnya bertanya.Kelas harus melakukan satu hal ketika dilihat pada tingkat abstraksinya sendiri. Tidak diragukan lagi akan melakukan banyak hal pada tingkat yang kurang abstrak. Beginilah cara kerja kelas untuk membuat program lebih mudah dikelola: mereka menyembunyikan detail implementasi jika Anda tidak perlu memeriksanya dengan cermat.
Saya menggunakan nama kelas sebagai ujian untuk ini. Jika saya tidak bisa memberi kelas nama deskriptif yang cukup pendek, atau jika nama itu memiliki kata seperti "Dan" di dalamnya, saya mungkin melanggar Prinsip Tanggung Jawab Tunggal.
Dalam pengalaman saya, lebih mudah untuk menjaga prinsip ini di tingkat yang lebih rendah, di mana segalanya lebih konkret.
sumber
Ini tentang memiliki satu peran unik .
Setiap kelas harus dilanjutkan dengan nama peran. Peran sebenarnya adalah (set) kata kerja yang terkait dengan konteks.
Sebagai contoh :
File menyediakan akses file. FileManager mengelola objek File.
Resource menahan data untuk satu sumber daya dari File. ResourceManager memegang dan menyediakan semua Sumber Daya.
Di sini Anda dapat melihat bahwa beberapa kata kerja seperti "kelola" menyiratkan seperangkat kata kerja lainnya. Kata kerja saja lebih baik dianggap sebagai fungsi daripada kelas, sebagian besar waktu. Jika kata kerja menyiratkan terlalu banyak tindakan yang memiliki konteks bersama sendiri, maka itu harus menjadi kelas itu sendiri.
Jadi, idenya adalah hanya untuk memberi Anda ide sederhana tentang apa yang dilakukan kelas dengan mendefinisikan peran unik, yang mungkin merupakan agregat beberapa sub-peran (yang dilakukan oleh objek anggota atau objek lain).
Saya sering membangun kelas Manajer yang memiliki beberapa kelas lain di dalamnya. Seperti Pabrik, Register, dll. Lihatlah kelas Manajer seperti semacam ketua kelompok, ketua orkestra yang membimbing orang lain untuk bekerja sama untuk mencapai gagasan tingkat tinggi. Dia memiliki satu peran, tetapi menyiratkan bekerja dengan peran unik lainnya di dalam. Anda juga dapat melihatnya seperti bagaimana sebuah perusahaan diorganisasikan: seorang CEO bukan yang produktif pada tingkat produktivitas murni, tetapi jika dia tidak ada di sana, maka tidak ada yang dapat bekerja dengan benar bersama. Itu perannya.
Saat Anda mendesain, kenali peran unik. Dan untuk setiap peran, lihat lagi apakah itu tidak dapat dipotong di beberapa peran lainnya. Dengan begitu, jika Anda perlu mengubah cara Manajer Anda membangun objek, simak saja Pabrik dan pergi dengan tenang.
sumber
SRP tidak hanya tentang membagi kelas, tetapi juga tentang mendelegasikan fungsi.
Dalam contoh Dog yang digunakan di atas, jangan gunakan SRP sebagai pembenaran untuk memiliki 3 kelas terpisah seperti DogBarker, DogWalker, dll (kohesi rendah). Sebagai gantinya, lihat implementasi metode kelas dan putuskan apakah mereka "tahu terlalu banyak". Anda masih dapat memiliki dog.walk (), tetapi mungkin metode walk () harus mendelegasikan ke kelas lain rincian tentang cara berjalan.
Akibatnya, kami mengizinkan kelas Anjing memiliki satu alasan untuk berubah: karena Anjing berubah. Tentu saja, menggabungkan ini dengan prinsip-prinsip SOLID lainnya Anda akan Memperpanjang Anjing untuk fungsionalitas baru daripada mengubah Anjing (terbuka / tertutup). Dan Anda akan Menyuntikkan dependensi Anda seperti IMove dan IEat. Dan tentu saja Anda akan membuat antarmuka terpisah ini (segregasi antarmuka). Anjing hanya akan berubah jika kami menemukan bug atau jika Anjing berubah secara mendasar (Sub Liskov, jangan memperpanjang dan kemudian menghapus perilaku).
Efek bersih dari SOLID adalah kita dapat menulis kode baru lebih sering daripada kita harus memodifikasi kode yang ada, dan itu adalah kemenangan besar.
sumber
Itu semua tergantung pada definisi tanggung jawab dan bagaimana definisi itu akan berdampak pada pemeliharaan kode Anda. Semuanya bermuara pada satu hal dan itulah bagaimana desain Anda akan membantu Anda dalam menjaga kode Anda.
Dan Seperti seseorang berkata "Berjalan di atas air dan merancang perangkat lunak dari spesifikasi yang diberikan itu mudah, asalkan keduanya dibekukan".
Jadi, jika kita mendefinisikan tanggung jawab dengan cara yang lebih konkret, maka kita tidak perlu mengubahnya.
Kadang-kadang tanggung jawab akan sangat jelas, tetapi kadang-kadang mungkin halus dan kita perlu memutuskan dengan bijaksana.
Misalkan, kita menambahkan tanggung jawab lain ke kelas Dog bernama catchThief (). Sekarang mungkin mengarah ke tanggung jawab tambahan yang berbeda. Besok, jika cara Anjing menangkap pencuri harus diubah oleh Departemen Kepolisian, maka kelas Anjing harus diubah. Akan lebih baik untuk membuat subclass lain dalam kasus ini dan beri nama ThiefCathcerDog. Tetapi dalam pandangan yang berbeda, jika kita yakin bahwa itu tidak akan berubah dalam keadaan apa pun, atau cara catchThief telah dilaksanakan tergantung pada beberapa parameter eksternal, maka sangat baik untuk memiliki tanggung jawab ini. Jika tanggung jawab tidak aneh, maka kita harus memutuskannya secara bijaksana berdasarkan use case.
sumber
"Satu alasan untuk berubah" tergantung pada siapa yang menggunakan sistem. Pastikan Anda meminta Anda menggunakan case untuk setiap aktor, dan membuat daftar perubahan yang paling mungkin, untuk setiap kemungkinan perubahan use case pastikan hanya satu kelas yang akan terpengaruh oleh perubahan itu. Jika Anda menambahkan kasus penggunaan yang sama sekali baru, pastikan Anda hanya perlu memperpanjang kelas untuk melakukannya.
sumber