Apakah desain kelas ini melanggar prinsip tanggung jawab tunggal?

63

Hari ini saya bertengkar dengan seseorang.

Saya sedang menjelaskan manfaat memiliki model domain yang kaya dibandingkan dengan model domain yang anemia. Dan saya menunjukkan poin saya dengan kelas sederhana yang terlihat seperti itu:

public class Employee
{
    public Employee(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastname;
    }

    public string FirstName { get private set; }
    public string LastName { get; private set;}
    public int CountPaidDaysOffGranted { get; private set;}

    public void AddPaidDaysOffGranted(int numberOfdays)
    {
        // Do stuff
    }
}

Saat dia membela pendekatan model anemianya, salah satu argumennya adalah: "Saya seorang yang percaya pada SOLID . Anda melanggar prinsip tanggung jawab tunggal (SRP) karena Anda berdua mewakili data dan melakukan logika di kelas yang sama."

Saya menemukan klaim ini benar-benar mengejutkan, karena mengikuti alasan ini, setiap kelas yang memiliki satu properti dan satu metode melanggar SRP, dan karena itu OOP secara umum tidak SOLID, dan pemrograman fungsional adalah satu-satunya jalan ke surga.

Saya memutuskan untuk tidak menjawab banyak argumennya, tetapi saya ingin tahu apa pendapat masyarakat tentang pertanyaan ini.

Jika saya menjawab, saya akan mulai dengan menunjuk pada paradoks yang disebutkan di atas, dan kemudian menunjukkan bahwa SRP sangat tergantung pada tingkat granularitas yang ingin Anda pertimbangkan dan bahwa jika Anda mengambilnya cukup jauh, setiap kelas yang berisi lebih dari satu properti atau satu metode melanggarnya.

Apa yang akan kamu katakan?

Pembaruan: Contoh ini telah dimutakhirkan dengan murah hati oleh guntbert untuk membuat metode ini lebih realistis dan membantu kami fokus pada diskusi yang mendasarinya.

tobiak777
sumber
19
kelas ini melanggar SRP bukan karena mencampurkan data dengan logika, tetapi karena memiliki kohesi yang rendah - objek potensial Tuhan
nyamuk
1
Apa tujuan menambahkan liburan ke karyawan? Mungkin harus ada kelas kalender atau sesuatu yang memiliki liburan. Saya pikir teman Anda benar.
James Black
9
Jangan pernah mendengarkan seseorang yang mengatakan "Saya seorang yang percaya pada X".
Stig Hemmer
29
Ini bukan masalah apakah ini merupakan pelanggaran terhadap SRP seperti apakah itu pemodelan yang baik sama sekali. Misalkan saya seorang karyawan. Ketika saya bertanya kepada manajer saya apakah tidak masalah dengannya jika saya menghabiskan akhir pekan yang panjang untuk bermain ski, manajer saya tidak menambahkan liburan kepada saya . Kode di sini tidak cocok dengan kenyataan yang ingin dimodelkan, dan oleh karena itu mencurigakan.
Eric Lippert
2
+1 untuk pemrograman fungsional menjadi satu-satunya jalan ke surga.
erip

Jawaban:

68

Tanggung jawab tunggal harus dipahami sebagai abstraksi tugas logis dalam sistem Anda. Kelas harus memiliki tanggung jawab tunggal untuk (melakukan semua yang diperlukan untuk) melakukan satu tugas tunggal. Ini benar-benar dapat membawa banyak ke kelas yang dirancang dengan baik, tergantung pada apa tanggung jawabnya. Kelas yang menjalankan mesin skrip Anda, misalnya, dapat memiliki banyak metode dan data yang terlibat dalam pemrosesan skrip.

Rekan kerja Anda berfokus pada hal yang salah. Pertanyaannya bukan "anggota apa yang dimiliki kelas ini?" tetapi "operasi bermanfaat apa yang dilakukan kelas ini dalam program?" Setelah itu dipahami, model domain Anda terlihat baik-baik saja.

Mason Wheeler
sumber
Jika Pemrograman Berorientasi Objek dibuat dan dimaksudkan untuk memodelkan objek dan kelas kehidupan nyata (tindakan + atribut) maka mengapa tidak menulis kelas kode yang memiliki banyak tanggung jawab (tindakan)? Objek dunia nyata dapat memiliki banyak tanggung jawab. Misalnya seorang jurnalis menulis editorial di surat kabar dan mewawancarai politisi di sebuah acara TV. Dua tanggung jawab untuk objek kehidupan nyata! Bagaimana jika saya akan menulis jurnalis kelas?
user1451111
41

Prinsip Tanggung Jawab Tunggal hanya peduli apakah sepotong kode (dalam OOP, biasanya kita berbicara tentang kelas) memiliki tanggung jawab atas satu fungsi . Saya pikir teman Anda mengatakan bahwa fungsi dan data tidak dapat digabungkan, tidak benar-benar memahami gagasan itu. Jika Employeejuga berisi informasi tentang tempat kerjanya, seberapa cepat mobilnya berjalan, dan jenis makanan apa yang dimakan anjingnya maka kita akan mengalami masalah.

Karena kelas ini hanya berurusan dengan Employee, saya pikir itu adil untuk mengatakan itu tidak melanggar SRP terang-terangan, tetapi orang selalu akan memiliki pendapat mereka sendiri.

Satu tempat di mana kita dapat meningkatkan adalah untuk memisahkan informasi Karyawan (seperti nama, nomor telepon, email) dari liburannya. Dalam pikiran saya, ini tidak berarti bahwa metode dan data tidak dapat saling berbaur , itu hanya berarti bahwa mungkin fungsi liburan bisa berada di tempat yang terpisah.

Frank Bryce
sumber
20

Menurut saya kelas ini berpotensi melanggar SRP jika terus mewakili Employeedan EmployeeHolidays.

Seperti itu, dan jika itu datang kepada saya untuk Peer Review, saya mungkin akan membiarkannya. Jika lebih banyak properti dan metode spesifik Karyawan ditambahkan, dan lebih banyak properti khusus liburan ditambahkan, saya mungkin akan menyarankan pemisahan, mengutip SRP dan ISP.

NikolaiDante
sumber
Saya setuju. Jika kodenya sesederhana yang disediakan di sini, saya mungkin akan membiarkannya. Tetapi dalam pikiran saya, saya seharusnya tidak menjadi tanggung jawab Karyawan untuk menangani liburannya sendiri. Ini mungkin tidak tampak seperti masalah besar di mana tanggung jawab ditempatkan, tetapi lihatlah seperti ini: Jika Anda baru mengenal basis kode, dan harus bekerja pada fungsionalitas khusus liburan - di mana Anda akan melihat pertama? Untuk logika liburan, saya pribadi TIDAK akan melihat entitas Karyawan untuk memulai.
Niklas H
1
@ Niklas Setuju. Secara pribadi saya tidak akan melihat secara acak dan mencoba menebak kelas yang akan saya cari di studio dengan kata "Liburan" dan melihat kelas apa yang muncul. :)
NikolaiDante
4
Benar. Tetapi bagaimana jika itu tidak disebut "Liburan" dalam sistem baru ini, tetapi adalah "Liburan" atau "Waktu Bebas". Tapi saya setuju, dengan itu Anda biasanya memiliki kemampuan untuk hanya mencarinya, atau dapat meminta rekan kerja. Komentar saya terutama untuk mendapatkan OP untuk memodelkan mental tanggung jawab dan di mana tempat yang paling jelas untuk logika adalah :-)
Niklas H
Saya setuju dengan pernyataan pertama Anda. Namun, jika datang ke peer review, saya tidak berpikir saya akan melakukannya karena melanggar SRP adalah lereng yang licin dan ini bisa menjadi yang pertama dari banyak jendela yang rusak. Tepuk tangan.
Jim Speaker
20

Sudah ada jawaban bagus yang menunjukkan bahwa SRP adalah konsep abstrak tentang fungsi logis, tetapi ada poin yang lebih halus yang menurut saya layak ditambahkan.

Dua huruf pertama dalam SOLID, SRP dan OCP, keduanya tentang bagaimana kode Anda berubah sebagai respons terhadap perubahan persyaratan. Definisi favorit saya tentang SRP adalah: "modul / kelas / fungsi seharusnya hanya memiliki satu alasan untuk berubah." Berdebat tentang kemungkinan alasan untuk mengubah kode Anda jauh lebih produktif daripada berdebat tentang apakah kode Anda SOLID atau tidak.

Berapa banyak alasan yang harus diubah oleh kelas Karyawan Anda? Saya tidak tahu, karena saya tidak tahu konteks di mana Anda menggunakannya, dan saya juga tidak bisa melihat masa depan. Yang bisa saya lakukan adalah bertukar pikiran tentang kemungkinan perubahan berdasarkan apa yang saya lihat di masa lalu, dan Anda dapat menilai secara subjektif seberapa besar kemungkinannya. Jika lebih dari satu skor antara "kemungkinan masuk akal" dan "kode saya telah berubah karena alasan yang tepat", maka Anda melanggar SRP terhadap perubahan semacam itu. Ini satu: seseorang dengan lebih dari dua nama bergabung dengan perusahaan Anda (atau seorang arsitek membaca artikel W3C yang luar biasa ini ). Ini satu lagi: perusahaan Anda mengubah cara mengalokasikan hari libur.

Perhatikan bahwa alasan ini sama-sama valid bahkan jika Anda menghapus metode AddHolidays. Banyak model domain anemia yang melanggar SRP. Banyak dari mereka hanya database-tables-in-code, dan sangat umum untuk tabel database memiliki 20 alasan untuk berubah.

Ini adalah sesuatu untuk dikunyah: apakah kelas Karyawan Anda akan berubah jika sistem Anda perlu melacak gaji karyawan? Alamat? Info kontak darurat? Jika Anda mengatakan "ya" (dan "kemungkinan akan terjadi") pada dua dari itu, maka kelas Anda akan melanggar SRP bahkan jika belum ada kode di dalamnya! SOLID adalah tentang proses dan arsitektur sebanyak ini tentang kode.

Carl Leth
sumber
9

Bahwa kelas mewakili data bukan tanggung jawab kelas, itu adalah detail implementasi pribadi.

Kelas memiliki satu tanggung jawab, untuk mewakili seorang karyawan. Dalam konteks ini, itu berarti menyajikan beberapa API publik yang memberi Anda fungsionalitas yang Anda butuhkan untuk berurusan dengan karyawan (apakah AddHolidays adalah contoh yang baik masih bisa diperdebatkan).

Implementasinya internal; Kebetulan ini membutuhkan beberapa variabel pribadi dan beberapa logika. Itu tidak berarti bahwa kelas sekarang memiliki banyak tanggung jawab.

RemcoGerlich
sumber
Garis pemikiran yang menarik, terima kasih banyak untuk berbagi
tobiak777
Bagus - Cara yang baik untuk mengekspresikan tujuan OOP yang dimaksud.
user949300
5

Gagasan bahwa mencampur logika dan data dengan cara apa pun selalu salah, sangat konyol bahkan tidak pantas didiskusikan. Namun, memang ada pelanggaran yang jelas dari tanggung jawab tunggal dalam contoh, tetapi itu bukan karena ada properti DaysOfHolidaysdan fungsi AddHolidays(int).

Itu karena identitas karyawan bercampur dengan manajemen liburan, yang buruk. Identitas karyawan adalah hal kompleks yang diperlukan untuk melacak liburan, gaji, lembur, untuk mewakili siapa di tim mana, untuk menautkan ke laporan kinerja, dll. Seorang karyawan juga dapat mengubah nama depan, nama belakang, atau keduanya, dan tetap sama karyawan. Karyawan bahkan mungkin memiliki beberapa ejaan nama mereka seperti ASCII dan ejaan unicode. Orang dapat memiliki 0 hingga n nama depan dan / atau belakang. Mereka mungkin memiliki nama yang berbeda di yurisdiksi yang berbeda. Melacak identitas karyawan adalah cukup tanggung jawab sehingga manajemen liburan atau liburan tidak dapat ditambahkan tanpa menyebutnya tanggung jawab kedua.

Peter
sumber
"Melacak identitas karyawan adalah cukup tanggung jawab sehingga manajemen liburan atau liburan tidak dapat ditambahkan tanpa menyebutnya tanggung jawab kedua." + Pegawai mungkin memiliki beberapa nama dll ... Maksud dari sebuah model adalah untuk fokus pada fakta-fakta relevan dari dunia nyata untuk masalah yang dihadapi. Ada persyaratan untuk model ini yang optimal. Dalam persyaratan ini, Karyawan hanya menarik sejauh kita dapat memodifikasi liburan mereka dan kami tidak terlalu tertarik dalam mengelola aspek-aspek lain dari kehidupan nyata mereka.
tobiak777
@reddy "Karyawan hanya menarik sejauh kita dapat mengubah liburan mereka" - Yang berarti Anda harus mengidentifikasi mereka dengan benar. Segera setelah Anda memiliki karyawan, mereka dapat mengubah nama belakang mereka kapan saja karena pernikahan atau perceraian. Mereka juga dapat mengubah nama dan jenis kelamin mereka. Akankah Anda memecat karyawan jika nama belakang mereka berubah sedemikian rupa sehingga nama mereka cocok dengan karyawan lain? Anda tidak akan menambahkan semua fungsi ini sekarang. Sebaliknya Anda akan menambahkannya saat Anda membutuhkannya, yang mana bagus. Terlepas dari berapa banyak yang diterapkan, tanggung jawab identifikasi tetap sama.
Peter
3

"Saya seorang yang percaya pada SOLID. Anda melanggar prinsip tanggung jawab tunggal (SRP) karena Anda berdua mewakili data dan melakukan logika di kelas yang sama."

Seperti yang lain, saya tidak setuju dengan ini.

Saya akan mengatakan bahwa SRP dilanggar jika Anda melakukan lebih dari satu bagian logika di kelas. Berapa banyak data yang perlu disimpan dalam kelas untuk mencapai satu potongan logika itu tidak relevan.

Lightness Races with Monica
sumber
Tidak! SRP tidak dilanggar oleh beberapa bagian logika, banyak bagian data atau kombinasi keduanya. Satu-satunya persyaratan adalah objek harus tetap pada tujuannya. Tujuannya dapat berpotensi memerlukan banyak operasi.
Martin Maat
@ MartinMaat: Banyak operasi, ya. Salah satu bagian dari logika sebagai hasilnya. Saya pikir kami mengatakan hal yang sama tetapi dengan istilah yang berbeda (dan saya senang menganggap bahwa Anda adalah yang benar karena saya tidak sering mempelajari hal ini)
Lightness Races with Monica
2

Saya tidak merasa berguna akhir-akhir ini untuk berdebat tentang apa yang dilakukan dan tidak merupakan tanggung jawab tunggal atau satu alasan untuk berubah. Saya akan mengusulkan Prinsip Duka Minimum sebagai gantinya:

Prinsip Duka Minimum: kode harus berupaya meminimalkan kemungkinannya untuk membutuhkan perubahan atau memaksimalkan kemudahan untuk diubah.

Bagaimana dengan itu? Seharusnya tidak mengambil ilmuwan roket untuk mencari tahu mengapa ini dapat membantu mengurangi biaya perawatan dan mudah-mudahan itu tidak menjadi titik perdebatan tanpa akhir, tetapi seperti dengan SOLID pada umumnya, itu bukan sesuatu untuk diterapkan secara membabi buta di mana-mana. Itu adalah sesuatu yang perlu dipertimbangkan sambil menyeimbangkan pertukaran.

Adapun kemungkinan membutuhkan perubahan, itu turun dengan:

  1. Pengujian yang baik (peningkatan keandalan).
  2. Melibatkan hanya kode minimum yang diperlukan untuk melakukan sesuatu yang spesifik (ini dapat termasuk mengurangi kopling aferen).
  3. Hanya membuat kode badass sesuai fungsinya (lihat Prinsip Membuat Badass).

Adapun kesulitan membuat perubahan, itu naik dengan kopling eferen. Pengujian memperkenalkan kopling eferen tetapi meningkatkan keandalan. Dilakukan dengan baik, umumnya lebih baik daripada merugikan dan benar-benar dapat diterima dan dipromosikan oleh Prinsip Duka Minimum.

Make Badass Principle: kelas yang digunakan di banyak tempat harus mengagumkan. Mereka harus dapat diandalkan, efisien jika itu terkait dengan kualitas mereka, dll.

Dan Prinsip Buat Badass terikat dengan Prinsip Duka Minimum, karena hal-hal badass akan menemukan kemungkinan lebih rendah untuk membutuhkan perubahan daripada hal-hal yang menyedot apa yang mereka lakukan.

Saya akan mulai dengan menunjuk pada paradoks yang disebutkan di atas, dan kemudian menunjukkan bahwa SRP sangat tergantung pada tingkat granularitas yang ingin Anda pertimbangkan dan bahwa jika Anda mengambilnya cukup jauh, setiap kelas yang mengandung lebih dari satu properti atau satu metode melanggar saya t.

Dari sudut pandang SRP, sebuah kelas yang nyaris tidak melakukan apa pun tentu hanya memiliki satu (kadang-kadang nol) alasan untuk berubah:

class Float
{
public:
    explicit Float(float val);
    float get() const;
    void set(float new_val);
};

Itu praktis tidak memiliki alasan untuk berubah! Lebih baik dari SRP. Ini ZRP!

Kecuali saya sarankan itu adalah pelanggaran terang-terangan terhadap Prinsip Make Badass. Ini juga sama sekali tidak berharga. Sesuatu yang tidak begitu banyak berharap tidak bisa menjadi badass. Ini memiliki terlalu sedikit informasi (TLI). Dan tentu saja ketika Anda memiliki sesuatu yang TLI, ia tidak dapat melakukan sesuatu yang benar-benar bermakna, bahkan dengan informasi yang dirangkumnya, sehingga ia harus membocorkannya ke dunia luar dengan harapan bahwa orang lain benar-benar akan melakukan sesuatu yang bermakna dan sombong. Dan kebocoran itu oke untuk sesuatu yang hanya dimaksudkan untuk mengumpulkan data dan tidak lebih, tetapi ambang itu adalah perbedaan seperti yang saya lihat antara "data" dan "objek".

Tentu saja sesuatu yang merupakan TMI juga buruk. Kami mungkin menempatkan seluruh perangkat lunak kami dalam satu kelas. Bahkan dapat memiliki satu runmetode. Dan seseorang bahkan mungkin berpendapat bahwa sekarang ada satu alasan yang sangat luas untuk berubah: "Kelas ini hanya perlu diubah jika perangkat lunak perlu ditingkatkan." Saya bodoh, tapi tentu saja kita bisa membayangkan semua masalah pemeliharaan dengan itu.

Jadi ada tindakan keseimbangan untuk granularity atau kekasaran objek yang Anda desain. Saya sering menilai dengan seberapa banyak informasi yang Anda miliki bocor ke dunia luar, dan seberapa banyak fungsionalitas bermakna yang dapat dilakukan. Saya sering mendapati Prinsip Make Badass bermanfaat di sana untuk menemukan keseimbangan sambil menggabungkannya dengan Prinsip Duka Minimum.


sumber
1

Sebaliknya, bagi saya model domain Anemik mematahkan beberapa konsep utama OOP (yang menyatukan atribut dan perilaku), tetapi dapat dihindari berdasarkan pilihan arsitektur. Domain anemia lebih mudah dipikirkan, kurang organik, dan lebih berurutan.

Banyak sistem cenderung melakukan ini ketika banyak lapisan harus bermain dengan data yang sama (lapisan layanan, lapisan web, lapisan klien, agen ...).

Lebih mudah untuk mendefinisikan struktur data di satu tempat dan perilaku di kelas layanan lainnya. Jika kelas yang sama digunakan pada beberapa lapisan, yang satu ini dapat tumbuh besar, dan ia menanyakan pertanyaan lapisan mana yang bertanggung jawab untuk menentukan perilaku yang dibutuhkan, dan siapa yang dapat memanggil metode.

Sebagai contoh, itu mungkin bukan ide yang baik daripada proses Agen yang menghitung statistik pada semua karyawan Anda dapat memanggil menghitung untuk hari yang dibayar. Dan daftar karyawan GUI tentu saja tidak memerlukan perhitungan id agregat baru yang digunakan dalam agen statistik ini (dan data teknis yang menyertainya). Ketika Anda memisahkan metode dengan cara ini, Anda biasanya berakhir dengan kelas dengan hanya struktur data.

Anda dapat dengan mudah membuat serial / deserialize data "objek", atau hanya beberapa dari mereka, atau ke format lain (json) ... tanpa khawatir tentang konsep objek / tanggung jawab. Hanya data yang lewat. Anda selalu dapat melakukan pemetaan data antara dua kelas (Karyawan, EmployeeVO, EmployeeStatistic ...) tetapi apa yang dimaksud dengan Karyawan di sini?

Jadi ya itu benar-benar memisahkan data dalam kelas domain dan penanganan data dalam kelas layanan tetapi di sini diperlukan. Sistem seperti itu sekaligus fungsional untuk membawa nilai bisnis dan teknis juga untuk menyebarkan data ke mana pun dibutuhkan, sambil menjaga ruang lingkup tanggung jawab yang tepat (dan objek yang didistribusikan juga tidak menyelesaikan masalah ini).

Jika Anda tidak perlu memisahkan lingkup perilaku Anda lebih bebas untuk meletakkan metode di kelas layanan atau di kelas domain, tergantung pada bagaimana Anda melihat objek Anda. Saya cenderung melihat objek sebagai konsep "nyata", ini secara alami membantu menjaga SRP. Jadi, dalam contoh Anda, itu lebih realistis daripada Bos Karyawan menambahkan hari gajian diberikan ke PayDayAccount-nya. Seorang Karyawan Dipekerjakan oleh perusahaan, Pekerjaan, bisa Sakit atau Diminta saran, dan dia memiliki akun Payday (Bos dapat mengambilnya langsung dari dia atau dari registry PayDayAccount ...) Tetapi Anda dapat membuat pintasan teragregasi di sini untuk kesederhanaan jika Anda tidak ingin terlalu banyak kerumitan untuk perangkat lunak sederhana.

Vince
sumber
Terima kasih atas masukan Anda, Vince. Intinya, Anda tidak perlu lapisan layanan saat memiliki domain kaya. Hanya ada satu kelas yang bertanggung jawab atas perilaku, dan itu adalah entitas domain Anda. Lapisan lain (lapisan web, UI dll ...) biasanya berurusan dengan DTO, dan ViewModels dan ini baik-baik saja. Model domain adalah untuk memodelkan domain, tidak melakukan pekerjaan UI, atau mengirim pesan di internet. Pesan Anda mencerminkan kesalahpahaman umum ini, yang berasal dari fakta bahwa orang tidak tahu cara menyesuaikan OOP dengan desain mereka. Dan saya pikir ini sangat menyedihkan - bagi mereka.
tobiak777
Saya tidak berpikir saya memiliki kesalahpahaman tentang domain kaya OOP, saya telah merancang banyak sotwares seperti itu, dan memang benar itu sangat bagus untuk pemeliharaan / evolusi. Tapi saya minta maaf untuk memberitahu Anda ini bukan peluru perak.
Vince
Saya tidak mengatakan itu. Untuk menulis kompiler mungkin bukan, tetapi untuk sebagian besar lini aplikasi bisnis / SaaS, saya pikir itu jauh lebih sedikit seni / sains daripada apa yang Anda sarankan. Kebutuhan akan model domain dapat dibuktikan secara matematis, contoh-contoh Anda membuat saya berpikir tentang desain yang dapat diperdebatkan daripada kekurangan keterbatasan dalam OOP
tobiak777
0

Anda melanggar prinsip tanggung jawab tunggal (SRP) karena Anda mewakili data dan melakukan logika di kelas yang sama.

Kedengarannya sangat masuk akal bagi saya. Model mungkin tidak memiliki properti publik jika mengekspos tindakan. Ini pada dasarnya ide Pemisahan Command-Query. Harap dicatat bahwa Command akan memiliki negara pribadi pasti.

Dmitry Nogin
sumber
0

Anda tidak dapat melanggar Prinsip Tanggung Jawab Tunggal karena itu hanya kriteria estetika, bukan aturan Alam. Jangan disesatkan oleh nama yang terdengar ilmiah dan huruf besar.

ZunTzu
sumber
1
Ini bukan jawaban yang nyata, tetapi akan lebih baik sebagai komentar untuk pertanyaan itu.
Jay Elston