Bagaimana kelas dapat memiliki banyak metode tanpa melanggar prinsip tanggung jawab tunggal

64

Prinsip tanggung jawab tunggal didefinisikan di wikipedia sebagai

Prinsip tanggung jawab tunggal adalah prinsip pemrograman komputer yang menyatakan bahwa setiap modul, kelas, atau fungsi harus memiliki tanggung jawab atas satu bagian dari fungsi yang disediakan oleh perangkat lunak, dan bahwa tanggung jawab harus sepenuhnya dienkapsulasi oleh kelas.

Jika suatu kelas hanya memiliki satu tanggung jawab, bagaimana ia dapat memiliki lebih dari 1 metode? Tidakkah setiap metode memiliki tanggung jawab yang berbeda, yang kemudian berarti bahwa kelas akan memiliki lebih dari 1 tanggung jawab.

Setiap contoh yang saya lihat menunjukkan prinsip tanggung jawab tunggal menggunakan kelas contoh yang hanya memiliki satu metode. Mungkin membantu untuk melihat contoh atau memiliki penjelasan tentang kelas dengan beberapa metode yang masih dapat dianggap memiliki satu tanggung jawab.

Angsa
sumber
11
Mengapa downvote? Sepertinya pertanyaan ideal untuk SE.SE; orang tersebut meneliti topik tersebut, dan berusaha membuat pertanyaan itu menjadi sangat jelas. Itu layak mendapat pujian sebagai gantinya.
Arseni Mourzenko
19
Downvote mungkin karena ini menjadi pertanyaan yang sudah ditanyakan dan dijawab beberapa kali, misalnya lihat softwareengineering.stackexchange.com/questions/345018/… . Menurut saya, itu tidak menambah aspek baru yang substansial.
Hans-Martin Mosner
9
Ini hanyalah reductio ad absurdum. Jika setiap kelas benar-benar hanya diperbolehkan satu metode, maka secara harfiah tidak ada program yang bisa melakukan lebih dari satu hal.
Darrel Hoffman
6
@ DarrelHoffman Itu tidak benar. Jika setiap kelas adalah functor dengan hanya metode "call ()", maka Anda pada dasarnya baru saja meniru pemrograman prosedural biasa dengan pemrograman berorientasi objek. Anda masih dapat melakukan apa pun yang bisa Anda lakukan jika tidak, karena metode "panggilan ()" kelas dapat memanggil banyak metode "panggilan ()" kelas lain.
Vaelus

Jawaban:

29

Tanggung jawab tunggal mungkin bukan sesuatu yang dapat dipenuhi fungsi tunggal.

 class Location { 
     public int getX() { 
         return x;
     } 
     public int getY() { 
         return y; 
     } 
 }

Kelas ini dapat melanggar prinsip tanggung jawab tunggal. Bukan karena memiliki dua fungsi, tetapi jika kode untuk getX()dan getY()harus memenuhi pemangku kepentingan yang berbeda yang mungkin menuntut perubahan. Jika Wakil Presiden Tn. X mengirim sekitar memo bahwa semua angka akan dinyatakan sebagai angka titik mengambang dan Direktur Akuntansi Ny. Y menegaskan bahwa semua angka yang ditinjau oleh departemennya akan tetap bilangan bulat terlepas dari apa yang dipikirkan dengan baik oleh Tn maka kelas ini sebaiknya memiliki satu ide tentang siapa yang bertanggung jawab karena semuanya akan membingungkan.

Jika SRP telah diikuti, akan menjadi jelas jika kelas Lokasi berkontribusi untuk hal-hal yang diungkapkan oleh X dan kelompoknya. Perjelas apa yang menjadi tanggung jawab kelas dan Anda tahu arahan apa yang mempengaruhi kelas ini. Jika keduanya berdampak pada kelas ini maka itu dirancang dengan buruk untuk meminimalkan dampak perubahan. "Kelas seharusnya hanya memiliki satu alasan untuk berubah" tidak berarti seluruh kelas hanya dapat melakukan satu hal kecil. Itu berarti saya seharusnya tidak dapat melihat kelas dan mengatakan bahwa Tuan X dan Ny. Y memiliki ketertarikan pada kelas ini.

Selain hal-hal seperti itu. Tidak, banyak metode baik-baik saja. Berikan saja nama yang menjelaskan metode apa yang termasuk dalam kelas dan apa yang tidak.

SRP Paman Bob lebih tentang Hukum Conway daripada Hukum Curly . Paman Bob menganjurkan menerapkan Hukum Curly (melakukan satu hal) untuk fungsi bukan kelas. SRP memperingatkan terhadap pencampuran alasan untuk berubah bersama. Hukum Conway mengatakan sistem akan mengikuti bagaimana arus informasi organisasi. Itu mengarah pada mengikuti SRP karena Anda tidak peduli dengan apa yang tidak pernah Anda dengar.

"Modul harus bertanggung jawab pada satu, dan hanya satu, aktor"

Robert C Martin - Arsitektur Bersih

Orang-orang terus menginginkan SRP menjadi alasan untuk membatasi ruang lingkup. Ada lebih banyak alasan untuk membatasi ruang lingkup daripada SRP. Saya selanjutnya membatasi ruang lingkup dengan menegaskan bahwa kelas adalah abstraksi yang dapat mengambil nama yang memastikan bahwa melihat ke dalam tidak akan mengejutkan Anda .

Anda bisa menerapkan Hukum Curly ke kelas. Anda di luar apa yang Paman Bob bicarakan tetapi Anda bisa melakukannya. Di mana Anda salah adalah ketika Anda mulai berpikir itu berarti satu fungsi. Itu seperti berpikir keluarga seharusnya hanya memiliki satu anak. Memiliki lebih dari satu anak tidak menghentikannya menjadi keluarga.

Jika Anda menerapkan hukum Curly ke kelas, semua yang ada di kelas harus tentang satu ide pemersatu. Gagasan itu bisa luas. Idenya mungkin kegigihan. Jika beberapa fungsi utilitas logging ada di sana, maka mereka jelas tidak pada tempatnya. Tidak masalah jika Mr X adalah satu-satunya yang peduli dengan kode ini.

Prinsip klasik untuk diterapkan di sini disebut Pemisahan Kekhawatiran . Jika Anda memisahkan semua kekhawatiran Anda, dapat dikatakan bahwa yang tersisa di satu tempat adalah satu perhatian. Itulah yang kami sebut ide ini sebelum film 1991 City Slickers memperkenalkan kami pada karakter Curly.

Ini baik Hanya saja apa yang Paman Bob sebut sebagai tanggung jawab bukan masalah. Tanggung jawab kepadanya bukanlah sesuatu yang Anda fokuskan. Itu adalah sesuatu yang bisa memaksa Anda untuk berubah. Anda dapat fokus pada satu masalah dan masih membuat kode yang bertanggung jawab kepada berbagai kelompok orang dengan agenda berbeda.

Mungkin Anda tidak peduli tentang itu. Baik. Berpikir bahwa memegang untuk "melakukan satu hal" akan menyelesaikan semua kesengsaraan desain Anda menunjukkan kurangnya imajinasi tentang apa yang "satu hal" dapat akhirnya menjadi. Alasan lain untuk membatasi ruang lingkup adalah organisasi. Anda dapat membuat banyak "satu hal" di dalam yang lain "satu hal" sampai Anda memiliki laci sampah penuh dengan segalanya. Saya sudah membicarakan hal itu sebelumnya

Tentu saja alasan klasik OOP untuk membatasi ruang lingkup adalah karena kelas memiliki bidang pribadi di dalamnya dan alih-alih menggunakan getter untuk membagikan data tersebut, kami menempatkan setiap metode yang membutuhkan data tersebut di kelas tempat mereka dapat menggunakan data secara pribadi. Banyak yang menemukan ini terlalu ketat untuk digunakan sebagai batasan ruang lingkup karena tidak setiap metode yang dimiliki bersama menggunakan bidang yang sama persis. Saya ingin memastikan bahwa ide apa pun yang menyatukan data menjadi ide yang sama yang menyatukan metode.

Cara fungsional untuk melihat ini adalah a.f(x)dan a.g(x)hanya f a (x) dan g a (x). Bukan dua fungsi tetapi rangkaian pasangan fungsi yang berbeda-beda. The abahkan tidak harus memiliki data di dalamnya. Itu bisa saja bagaimana Anda tahu mana fdan gimplementasi yang akan Anda gunakan. Fungsi yang berubah bersama menjadi satu. Itu polimorfisme lama yang bagus.

SRP hanyalah salah satu dari banyak alasan untuk membatasi ruang lingkup. Itu bagus. Tapi bukan satu-satunya.

candied_orange
sumber
25
Saya pikir jawaban ini membingungkan seseorang yang mencoba mencari tahu SRP. Pertempuran antara Tuan Presiden dan Nyonya Direktur tidak diselesaikan melalui cara teknis dan menggunakannya untuk membenarkan keputusan rekayasa adalah tidak masuk akal. Hukum Conway sedang beraksi.
whatsisname
8
@whatsisname Sebaliknya. SRP secara eksplisit dimaksudkan untuk diterapkan pada pemangku kepentingan. Itu tidak ada hubungannya dengan desain teknis. Anda mungkin tidak setuju dengan pendekatan itu, tetapi begitulah SRP awalnya didefinisikan oleh Paman Bob, dan dia harus mengulanginya lagi karena beberapa alasan, orang tampaknya tidak dapat memahami gagasan sederhana ini (pikiran, apakah itu sebenarnya berguna adalah pertanyaan yang sepenuhnya ortogonal).
Luaan
Hukum Curly, seperti yang dijelaskan oleh Tim Ottinger, menekankan bahwa variabel harus secara konsisten berarti satu hal. Bagi saya, SRP sedikit lebih kuat dari itu; sebuah kelas dapat secara konseptual mewakili "satu hal", namun melanggar SRP jika dua pendorong perubahan eksternal memperlakukan beberapa aspek "satu hal" dengan cara yang berbeda, atau prihatin dengan dua aspek yang berbeda. Masalahnya adalah salah satu pemodelan; Anda telah memilih untuk memodelkan sesuatu sebagai satu kelas, tetapi ada sesuatu tentang domain yang membuat pilihan itu bermasalah (hal-hal mulai menghalangi Anda ketika basis kode berkembang).
Filip Milovanović
2
@ FilipMilovanović Kesamaan yang saya lihat antara Hukum Conway dan SRP seperti cara Paman Bob menjelaskan SRP dalam bukunya Clean Architecture berasal dari asumsi bahwa organisasi tersebut memiliki bagan org asiklikal yang bersih. Ini adalah ide lama. Bahkan Alkitab memiliki kutipan di sini: "Tidak seorang pun dapat melayani dua tuan".
candied_orange
1
@TKK saya menghubungkannya (tidak menyamakannya) dengan hukum Conways, bukan hukum Curly. Saya membantah gagasan bahwa SRP adalah hukum Curly terutama karena Paman Bob mengatakannya sendiri dalam bukunya Arsitektur Bersih.
candied_orange
48

Kuncinya di sini adalah ruang lingkup , atau, jika Anda suka, rincian . Bagian dari fungsionalitas yang diwakili oleh kelas dapat lebih jauh dipisahkan menjadi bagian-bagian fungsionalitas, masing-masing bagian menjadi metode.

Ini sebuah contoh. Bayangkan Anda perlu membuat CSV dari suatu urutan. Jika Anda ingin mematuhi RFC 4180, akan butuh beberapa waktu untuk mengimplementasikan algoritme dan menangani semua kasing tepi.

Melakukannya dalam metode tunggal akan menghasilkan kode yang tidak akan mudah dibaca, dan terutama, metode ini akan melakukan beberapa hal sekaligus. Karena itu, Anda akan membaginya menjadi beberapa metode; misalnya, salah satu dari mereka mungkin bertanggung jawab untuk menghasilkan header, yaitu baris pertama CSV, sementara metode lain akan mengonversi nilai tipe apa pun menjadi representasi string yang cocok untuk format CSV, sementara yang lain akan menentukan apakah nilai harus dimasukkan ke dalam tanda kutip ganda.

Metode-metode itu memiliki tanggung jawab mereka sendiri. Metode yang memeriksa apakah ada kebutuhan untuk menambahkan tanda kutip ganda atau tidak memiliki sendiri, dan metode yang menghasilkan header memiliki satu. Ini adalah SRP yang diterapkan pada metode .

Sekarang, semua metode tersebut memiliki satu tujuan yang sama, yaitu, mengambil urutan, dan menghasilkan CSV. Ini adalah tanggung jawab tunggal kelas .


Pablo H berkomentar:

Contoh yang bagus, tapi saya merasa masih belum menjawab mengapa SRP memungkinkan kelas untuk memiliki lebih dari satu metode publik.

Memang. Contoh CSV yang saya berikan idealnya memiliki satu metode publik dan semua metode lainnya bersifat pribadi. Contoh yang lebih baik adalah dari antrian, diimplementasikan oleh Queuekelas. Kelas ini pada dasarnya berisi dua metode: push(juga disebut enqueue), dan pop(juga disebut dequeue).

  • Tanggung jawabnya Queue.pushadalah menambahkan objek ke ekor antrian.

  • Tanggung jawabnya Queue.popadalah untuk menghapus objek dari kepala antrian, dan menangani kasus di mana antrian kosong.

  • Tanggung jawab Queuekelas adalah menyediakan logika antrian.

Arseni Mourzenko
sumber
1
Contoh yang bagus, tapi saya merasa masih belum menjawab mengapa SRP memungkinkan kelas untuk memiliki lebih dari satu metode publik .
Pablo H
1
@PabloH: adil Saya menambahkan contoh lain di mana kelas memiliki dua metode.
Arseni Mourzenko
30

Fungsi adalah fungsi.

Tanggung jawab adalah tanggung jawab.

Seorang mekanik memiliki tanggung jawab untuk memperbaiki mobil, yang akan melibatkan diagnostik, beberapa tugas perawatan sederhana, beberapa pekerjaan perbaikan aktual, beberapa pendelegasian tugas kepada orang lain, dll.

Kelas kontainer (daftar, array, kamus, peta, dll) memiliki tanggung jawab untuk menyimpan objek, yang melibatkan menyimpannya, memungkinkan penyisipan, menyediakan akses, semacam pemesanan, dll.

Tanggung jawab tunggal tidak berarti hanya ada sedikit kode / fungsionalitas, itu berarti fungsi apa pun yang ada "milik bersama" di bawah tanggung jawab yang sama.

Peter
sumber
2
Setuju. @ Penulis Ronkainen - untuk mengikat dua jawaban. Dan untuk tanggung jawab yang bersarang, menggunakan analogi mekanik Anda, sebuah garasi memiliki tanggung jawab untuk pemeliharaan kendaraan. mekanik yang berbeda di garasi memiliki tanggung jawab untuk berbagai bagian mobil, tetapi masing-masing mekanik ini bekerja bersama dalam kohesi
wolfsshield
2
@ Wolfsshield, setuju. Mekanik yang hanya melakukan satu hal tidak berguna, tetapi mekanik yang memiliki tanggung jawab tunggal tidak (setidaknya perlu). Meskipun analogi kehidupan nyata tidak selalu yang terbaik untuk menggambarkan konsep OOP abstrak, penting untuk membedakan perbedaan-perbedaan ini. Saya percaya tidak memahami perbedaan adalah apa yang menciptakan kebingungan sejak awal.
Aulis Ronkainen
3
@AulisRonkainen Sementara itu terlihat, berbau, dan terasa seperti analogi, saya memang bermaksud menggunakan mekanik untuk menyoroti arti spesifik dari istilah Responsibility dalam SRP. Saya sepenuhnya setuju dengan jawaban Anda.
Peter
20

Tanggung jawab tunggal tidak selalu berarti hanya melakukan satu hal.

Ambil contoh kelas layanan Pengguna:

class UserService {
    public User Get(int id) { /* ... */ }
    public User[] List() { /* ... */ }

    public bool Create(User u) { /* ... */ }
    public bool Exists(int id) { /* ... */ }
    public bool Update(User u) { /* ... */ }
}

Kelas ini memiliki banyak metode tetapi tanggung jawabnya jelas. Ini memberikan akses ke catatan pengguna di penyimpanan data. Satu-satunya dependensi adalah model Pengguna dan penyimpanan data. Ini secara longgar digabungkan dan sangat kohesif, yang sebenarnya adalah apa yang SRP coba untuk membuat Anda berpikir.

SRP tidak harus bingung dengan "Prinsip pemisahan antarmuka" (lihat SOLID ). Prinsip Segregasi Antarmuka (ISP) mengatakan bahwa antarmuka yang lebih kecil dan ringan lebih disukai daripada antarmuka yang lebih umum. Go menggunakan banyak ISP di seluruh perpustakaan standarnya:

// Interface to read bytes from a stream
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Interface to write bytes to a stream
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Interface to convert an object into JSON
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

SRP dan ISP tentu terkait, tetapi yang satu tidak menyiratkan yang lain. ISP ada di level antarmuka dan SRP ada di level kelas. Jika suatu kelas mengimplementasikan beberapa antarmuka sederhana, itu mungkin tidak lagi hanya memiliki satu tanggung jawab.

Terima kasih kepada Luaan karena menunjukkan perbedaan antara ISP dan SRP.

Jesse
sumber
3
Sebenarnya, Anda menjelaskan prinsip pemisahan antarmuka ("I" dalam SOLID). SRP adalah binatang yang sangat berbeda.
Luaan
Selain itu, konvensi pengkodean apa yang Anda gunakan di sini? Saya akan mengharapkan objek UserService dan Userakan UpperCamelCase, tetapi metode Create , Existsdan Updatesaya akan membuat lowerCamelCase.
KlaymenDK
1
@KlaymenDK Anda benar, huruf besar hanya kebiasaan menggunakan Go (huruf besar = diekspor / publik, huruf kecil = pribadi)
Jesse
@Luaan Terima kasih telah menunjukkan itu, saya akan mengklarifikasi jawaban saya
Jesse
1
@KlaymenDK Banyak bahasa menggunakan PascalCase untuk metode dan juga kelas. C # misalnya.
Omegastick
15

Ada seorang koki di sebuah restoran. Satu-satunya tanggung jawabnya adalah memasak. Namun dia bisa memasak steak, kentang, brokoli, dan ratusan hal lainnya. Apakah Anda akan menyewa satu koki per hidangan di menu Anda? Atau satu koki untuk setiap komponen setiap hidangan? Atau seorang koki yang dapat memenuhi tanggung jawabnya sendiri: Untuk memasak?

Jika Anda meminta koki itu untuk melakukan penggajian juga, saat itulah Anda melanggar SRP.

gnasher729
sumber
4

Contoh tandingan: menyimpan status yang dapat diubah.

Misalkan Anda memiliki kelas paling sederhana yang pernah ada, yang tugasnya hanya menyimpan int.

public class State {
    private int i;


    public State(int i) { this.i = i; }
}

Jika Anda dibatasi hanya 1 metode, Anda bisa memiliki setState(), atau a getState(), kecuali jika Anda memecahkan enkapsulasi dan membuat ipublik.

  • Setter tidak berguna tanpa rajin (Anda tidak akan pernah bisa membaca informasinya)
  • Seorang pengambil tidak ada gunanya tanpa setter (Anda tidak akan pernah bisa mengubah informasi).

Jadi jelas, tanggung jawab tunggal ini mengharuskan memiliki setidaknya 2 metode di kelas ini. QED.

Alexander
sumber
4

Anda salah mengartikan prinsip tanggung jawab tunggal.

Tanggung jawab tunggal tidak sama dengan metode tunggal. Mereka memiliki arti yang berbeda. Dalam pengembangan perangkat lunak kita berbicara tentang kohesi . Fungsi (metode) yang memiliki kohesi tinggi "milik" bersama dan dapat dihitung sebagai melakukan tanggung jawab tunggal.

Terserah pengembang untuk merancang sistem sehingga prinsip tanggung jawab tunggal terpenuhi. Orang dapat melihat ini sebagai teknik abstraksi dan karena itu kadang-kadang masalah pendapat. Menerapkan prinsip tanggung jawab tunggal membuat kode terutama lebih mudah untuk diuji dan lebih mudah untuk memahami arsitektur dan desainnya.

Aulis Ronkainen
sumber
2

Seringkali bermanfaat (dalam bahasa apa pun, tetapi terutama dalam bahasa OO) untuk melihat berbagai hal dan mengaturnya dari sudut pandang data alih-alih fungsinya.

Dengan demikian, pertimbangkan tanggung jawab kelas untuk menjaga integritas dan memberikan bantuan untuk menggunakan data yang dimilikinya. Jelas ini lebih mudah dilakukan jika semua kode berada dalam satu kelas, daripada tersebar di beberapa kelas. Menambahkan dua poin lebih dapat dilakukan dengan andal, dan kode lebih mudah dipelihara, dengan Point add(Point p)metode di Pointkelas daripada memilikinya di tempat lain.

Dan khususnya, kelas tidak boleh mengekspos apa pun yang dapat menghasilkan data yang tidak konsisten atau salah. Misalnya, jika a Pointharus terletak di dalam bidang (0,0) hingga (127.127), konstruktor dan metode apa pun yang memodifikasi atau menghasilkan yang baru Pointmemiliki tanggung jawab memeriksa nilai yang diberikan dan menolak perubahan apa pun yang akan melanggar ini kebutuhan. (Seringkali sesuatu seperti a Pointakan berubah, dan memastikan bahwa tidak ada cara untuk memodifikasi Pointsetelah itu dibangun maka juga akan menjadi tanggung jawab kelas)

Perhatikan bahwa layering di sini sangat bisa diterima. Anda mungkin memiliki Pointkelas untuk berurusan dengan poin individu dan Polygonkelas untuk berurusan dengan satu set Points; ini masih memiliki tanggung jawab terpisah karena Polygonmendelegasikan semua tanggung jawab untuk berurusan dengan apa pun yang semata-mata berkaitan dengan Point(seperti memastikan suatu poin memiliki nilai xdan ynilai) ke Pointkelas.

Curt J. Sampson
sumber