Apakah menambahkan jenis kembali ke metode pembaruan melanggar "Prinsip Tanggung Jawab Tunggal"?

37

Saya memiliki metode yang memperbarui data karyawan dalam database. The Employeekelas berubah, sehingga "memperbarui" sarana objek sebenarnya untuk instantiate objek baru.

Saya ingin Updatemetode untuk kembali contoh baru dari Employeedengan data diperbarui, tapi karena sekarang saya dapat mengatakan tanggung jawab dari metode ini adalah untuk memperbarui data karyawan, dan mengambil dari database baru Employeeobjek , apakah itu melanggar Tanggung Jawab Single Prinsip ?

Catatan DB itu sendiri diperbarui. Kemudian, objek baru dipakai untuk mewakili catatan ini.

Sipo
sumber
5
Kode pseudo kecil bisa sangat berguna di sini: Apakah sebenarnya ada catatan DB baru yang dibuat, atau apakah catatan DB itu sendiri diperbarui, tetapi dalam kode "klien" objek baru dibuat karena kelas dimodelkan sebagai tidak berubah?
Martin Ba
Selain apa yang diminta Martin Bra, mengapa Anda mengembalikan contoh Karyawan saat Anda memperbarui database? Nilai instance Karyawan belum berubah dalam metode Pembaruan jadi mengapa mengembalikannya (penelepon sudah memiliki akses ke instance ...). Atau apakah metode pembaruan juga mengambil nilai (berpotensi berbeda) dari database?
Thorsal
1
@ Penulis: jika entitas data Anda tidak dapat diubah, mengembalikan sebuah instance dengan data yang diperbarui cukup banyak SOP, karena jika tidak Anda harus melakukan instantiasi dari data yang dimodifikasi itu sendiri.
mikołak
21
Compare-and-swapdan test-and-setmerupakan operasi mendasar dalam teori pemrograman multi-utas. Keduanya merupakan metode pembaruan dengan tipe pengembalian, dan tidak bisa berfungsi sebaliknya. Apakah ini melanggar konsep seperti pemisahan perintah-permintaan atau Prinsip Tanggung Jawab Tunggal? Ya, dan itulah intinya . SRP bukan hal yang baik secara universal, dan sebenarnya bisa berbahaya secara aktif.
MSalters
3
@MSalters: Tepat. Pemisahan perintah / kueri mengatakan bahwa dimungkinkan untuk mengeluarkan kueri yang dapat dikenali sebagai idempoten, dan mengeluarkan perintah tanpa harus menunggu jawaban, tetapi atom baca-modifikasi-tulis harus diakui sebagai kategori operasi ketiga.
supercat

Jawaban:

16

Seperti halnya aturan apa pun, saya pikir yang penting di sini adalah mempertimbangkan tujuan aturan, semangat, dan tidak terperosok dalam menganalisis dengan tepat bagaimana aturan itu ditulis dalam beberapa buku teks dan bagaimana menerapkannya pada kasus ini. Kami tidak perlu mendekati ini seperti pengacara. Tujuan peraturan ini adalah untuk membantu kami menulis program yang lebih baik. Bukannya tujuan program menulis adalah untuk menegakkan aturan.

Tujuan dari aturan tanggung jawab tunggal adalah untuk membuat program lebih mudah dipahami dan dipelihara dengan membuat setiap fungsi melakukan satu hal yang koheren dan mandiri.

Sebagai contoh, saya pernah menulis sebuah fungsi yang saya sebut sesuatu seperti "checkOrderStatus", yang menentukan apakah pesanan tertunda, dikirimkan, dipesan kembali, apa pun, dan mengembalikan kode yang menunjukkan. Kemudian programmer lain datang dan memodifikasi fungsi ini untuk juga memperbarui kuantitas saat pesanan dikirim. Ini sangat melanggar prinsip tanggung jawab tunggal. Programmer lain yang membaca kode itu nanti akan melihat nama fungsinya, melihat bagaimana nilai pengembalian digunakan, dan mungkin tidak pernah menduga bahwa ia melakukan pembaruan basis data. Seseorang yang perlu mendapatkan status pesanan tanpa memperbarui kuantitas yang ada akan berada dalam posisi yang canggung: haruskah ia menulis fungsi baru yang menggandakan bagian status pesanan? Tambahkan bendera untuk memberi tahu apakah akan melakukan pembaruan db? Dll

Di sisi lain, saya tidak akan menganggap apa yang merupakan "dua hal". Saya baru-baru ini menulis sebuah fungsi yang mengirimkan informasi pelanggan dari sistem kami ke sistem klien kami. Fungsi itu melakukan beberapa pemformatan ulang data untuk memenuhi persyaratan mereka. Misalnya, kami memiliki beberapa bidang yang mungkin nol pada basis data kami, tetapi tidak mengizinkan nol sehingga kami harus mengisi beberapa teks dummy, "tidak ditentukan" atau saya lupa kata-kata yang tepat. Bisa dibilang fungsi ini melakukan dua hal: memformat ulang data DAN mengirimkannya. Tapi saya sengaja menempatkan ini dalam satu fungsi daripada memiliki "memformat ulang" dan "mengirim" karena saya tidak ingin pernah, pernah mengirim tanpa memformat ulang. Saya tidak ingin seseorang menulis panggilan baru dan tidak menyadari bahwa ia harus memanggil reformat dan kemudian mengirim.

Dalam kasus Anda, perbarui basis data dan kembalikan gambar catatan yang dituliskan tampak seperti dua hal yang mungkin berjalan secara logis dan tak terelakkan. Saya tidak tahu detail aplikasi Anda, jadi saya tidak bisa mengatakan dengan pasti apakah ini ide yang bagus atau tidak, tetapi kedengarannya masuk akal.

Jika Anda membuat objek dalam memori yang menyimpan semua data untuk catatan, melakukan panggilan database untuk menulis ini, dan kemudian mengembalikan objek, ini sangat masuk akal. Anda memiliki benda di tangan Anda. Mengapa tidak mengembalikannya saja? Jika Anda tidak mengembalikan objek, bagaimana penelepon mendapatkannya? Apakah dia harus membaca database untuk mendapatkan objek yang baru saja Anda tulis? Tampaknya agak tidak efisien. Bagaimana dia menemukan catatan itu? Apakah Anda tahu kunci utama? Jika seseorang menyatakan bahwa itu "sah" untuk fungsi tulis untuk mengembalikan kunci utama sehingga Anda dapat membaca kembali catatan, mengapa tidak hanya mengembalikan seluruh catatan sehingga Anda tidak perlu melakukannya? Apa bedanya?

Di sisi lain, jika membuat objek adalah kumpulan pekerjaan yang sangat berbeda dari menulis catatan database, dan penelepon mungkin ingin melakukan penulisan tetapi tidak membuat objek, maka ini bisa menjadi pemborosan. Jika seorang penelepon mungkin menginginkan objek tetapi tidak melakukan penulisan, maka Anda harus memberikan cara lain untuk mendapatkan objek, yang bisa berarti menulis kode yang berlebihan.

Tapi saya pikir skenario 1 lebih mungkin, jadi saya katakan, mungkin tidak ada masalah.

Jay
sumber
Terima kasih atas semua jawaban, tetapi yang ini sangat membantu saya.
Sipo
Jika Anda tidak ingin mengirim tanpa memformat ulang, dan tidak ada gunanya memformat ulang di samping mengirim data nanti, maka mereka satu hal, bukan dua.
Joker_vD
public function sendDataInClientFormat() { formatDataForClient(); sendDataToClient(); } private function formatDataForClient() {...} private function sendDataToClient() {...}
CJ Dennis
@ CJDennis Tentu. Dan sebenarnya itulah yang saya lakukan: Fungsi untuk melakukan pemformatan, fungsi untuk melakukan pengiriman aktual, dan ada dua fungsi lain yang tidak akan saya bahas di sini. Kemudian satu fungsi tingkat atas untuk memanggil mereka semua dalam urutan yang tepat. Anda bisa mengatakan "format dan kirim" adalah satu operasi logis dan dengan demikian dapat digabungkan dalam satu fungsi. Jika Anda bersikeras bahwa itu dua, ok, maka tetap hal yang rasional untuk dilakukan adalah memiliki satu fungsi tingkat atas yang melakukan semuanya.
Jay
67

Konsep SRP adalah untuk menghentikan modul melakukan 2 hal berbeda ketika melakukannya secara individual membuat untuk pemeliharaan yang lebih baik dan lebih sedikit spageti dalam waktu. Seperti kata SRP "satu alasan untuk berubah".

Dalam kasus Anda, jika Anda memiliki rutin yang "memperbarui dan mengembalikan objek yang diperbarui", Anda masih mengubah objek satu kali - memberikannya 1 alasan untuk berubah. Bahwa Anda mengembalikan objek kembali tidak ada di sini atau di sana, Anda masih beroperasi pada objek tunggal itu. Kode memiliki tanggung jawab untuk satu, dan hanya satu, hal.

SRP tidak benar-benar mencoba mengurangi semua operasi menjadi satu panggilan, tetapi untuk mengurangi apa yang Anda operasikan dan bagaimana Anda mengoperasikannya. Jadi satu pembaruan yang mengembalikan objek yang diperbarui baik-baik saja.

gbjbaanb
sumber
7
Atau, ini bukan "tentang mencoba mengurangi semua operasi menjadi satu panggilan," tetapi untuk mengurangi berapa banyak kasus berbeda yang harus Anda pikirkan ketika menggunakan item tersebut.
jpmc26
46

Seperti biasa, ini masalah derajat. SRP harus menghentikan Anda dari menulis metode yang mengambil catatan dari database eksternal, melakukan transformasi Fourier cepat di atasnya dan memperbarui registri statistik global dengan hasilnya. Saya pikir hampir semua orang akan setuju bahwa hal-hal ini harus dilakukan dengan metode berbeda. Mendalilkan satu tanggung jawab tunggal untuk setiap metode hanyalah cara yang paling ekonomis dan mudah diingat untuk menyatakan hal itu.

Di ujung lain dari spektrum adalah metode yang menghasilkan informasi tentang keadaan suatu objek. Khas isActivetelah memberikan informasi ini sebagai tanggung jawab tunggal. Mungkin semua orang setuju bahwa ini baik-baik saja.

Sekarang, beberapa memperluas prinsip sejauh mereka menganggap mengembalikan bendera sukses tanggung jawab yang berbeda dari melakukan tindakan yang keberhasilannya dilaporkan. Di bawah interpretasi yang sangat ketat, ini benar, tetapi karena alternatifnya harus memanggil metode kedua untuk mendapatkan status keberhasilan, yang menyulitkan penelepon, banyak programmer yang baik-baik saja dengan mengembalikan kode sukses dari metode dengan efek samping.

Mengembalikan objek baru adalah selangkah lebih maju dalam perjalanan menuju beberapa tanggung jawab. Membutuhkan pemanggil untuk membuat panggilan kedua untuk seluruh objek sedikit lebih masuk akal daripada membutuhkan panggilan kedua hanya untuk melihat apakah yang pertama berhasil atau tidak. Namun, banyak programmer akan mempertimbangkan untuk mengembalikan hasil pembaruan dengan sangat baik. Meskipun hal ini dapat ditafsirkan sebagai dua tanggung jawab yang sedikit berbeda, sudah barang tentu bukan salah satu pelanggaran mengerikan yang mengilhami prinsip tersebut.

Kilian Foth
sumber
15
Jika Anda harus memanggil metode kedua untuk melihat apakah metode pertama berhasil, tidakkah Anda harus memanggil metode ketiga untuk mendapatkan hasil dari metode kedua? Maka jika Anda benar - benar menginginkan hasilnya, ya ...
3
Saya dapat menambahkan bahwa penerapan SRP yang terlalu bersemangat menyebabkan banyak kelas kecil, yang merupakan beban tersendiri. Seberapa besar beban tergantung pada lingkungan Anda, kompiler, IDE / alat pembantu dll.
Erik Alapää
8

Apakah itu melanggar Prinsip Tanggung Jawab Tunggal?

Belum tentu. Jika ada, ini melanggar prinsip pemisahan perintah-kueri .

Tanggung jawabnya adalah

perbarui data karyawan

dengan pemahaman implisit bahwa tanggung jawab ini mencakup status operasi; misalnya, jika operasi gagal, pengecualian dilemparkan, jika berhasil, karyawan pembaruan dikembalikan, dll.

Sekali lagi, ini semua masalah tingkat dan penilaian subyektif.


Jadi bagaimana dengan pemisahan perintah-permintaan?

Ya, prinsip itu ada, tetapi mengembalikan hasil pembaruan sebenarnya sangat umum.

(1) Java Set#add(E)menambahkan elemen dan mengembalikan inklusi sebelumnya di set.

if (visited.add(nodeToVisit)) {
    // perform operation once
}

Ini lebih efisien daripada alternatif CQS, yang mungkin harus melakukan dua pencarian.

if (!visited.contains(nodeToVisit)) {
    visited.add(nodeToVisit);
    // perform operation once
}

(2) Compare-and-swap , fetch-and-add , dan test-and-set adalah primitif umum yang memungkinkan pemrograman bersamaan ada. Pola-pola ini sering muncul, dari instruksi CPU tingkat rendah hingga koleksi bersamaan tingkat tinggi.

Paul Draper
sumber
2

Tanggung jawab tunggal adalah tentang kelas yang tidak perlu diubah karena lebih dari satu alasan.

Sebagai contoh, seorang karyawan memiliki daftar nomor telepon. Ketika cara Anda menangani nomor telepon berubah (Anda dapat menambahkan kode panggilan negara) yang seharusnya tidak mengubah kelas karyawan sama sekali.

Saya tidak akan memiliki kelas karyawan yang perlu tahu bagaimana cara menyimpan sendiri ke database, karena itu akan berubah untuk perubahan karyawan dan perubahan dalam bagaimana data disimpan.

Serupa seharusnya tidak ada metode CalculateSalary di kelas karyawan. Properti gaji tidak apa-apa, tetapi perhitungan pajak dll. Harus dilakukan di tempat lain.

Tetapi metode Pembaruan mengembalikan apa yang baru saja diperbarui baik-baik saja.

Bengkok
sumber
2

Wrt. kasus spesifik:

Employee Update(Employee, name, password, etc) (sebenarnya menggunakan Builder karena saya memiliki banyak parameter).

Ini tampaknya yang UpdateCara mengambil yang ada Employeesebagai parameter pertama yang mengidentifikasi (?) Karyawan yang ada dan satu set parameter perubahan pada karyawan ini.

Saya tidak pikir ini bisa menjadi dilakukan bersih. Saya melihat dua kasus:

(a) Employee benar-benar berisi database / ID unik yang dengannya selalu dapat diidentifikasi dalam database. (Yaitu, Anda tidak perlu seluruh nilai rekaman ditetapkan untuk menemukannya di DB.

Dalam hal ini, saya lebih suka void Update(Employee obj)metode, yang hanya menemukan catatan yang ada dengan ID dan kemudian memperbarui bidang dari objek yang diteruskan. Atau mungkin sebuahvoid Update(ID, EmployeeBuilder values)

Variasi dari hal ini yang menurut saya berguna adalah hanya memiliki void Put(Employee obj)metode yang memasukkan atau memperbarui, tergantung pada apakah ada catatan (berdasarkan ID).

(b) Rekor yang ada penuh diperlukan untuk DB lookup, dalam kasus ini mungkin masih lebih masuk akal untuk memiliki: void Update(Employee existing, Employee newData).

Sejauh yang saya bisa lihat di sini, saya memang akan mengatakan bahwa tanggung jawab membangun objek baru (atau nilai-nilai sub-objek) untuk menyimpan dan benar-benar menyimpannya adalah ortogonal, jadi saya akan memisahkannya.

Persyaratan konkuren yang disebutkan dalam jawaban lain (set atom dan ambil / bandingkan-swap, dll) belum menjadi masalah dalam kode DB yang saya kerjakan sejauh ini. Ketika berbicara dengan DB, saya pikir ini harus ditangani pada tingkat transaksi secara normal, bukan pada tingkat pernyataan individu. (Bukan untuk mengatakan bahwa mungkin tidak ada desain di mana "atom" Employee[existing data] Update(ID, Employee newData)tidak masuk akal, tetapi dengan DB mengaksesnya bukan sesuatu yang biasanya saya lihat.)

Martin Ba
sumber
1

Sejauh ini semua orang di sini berbicara tentang kelas. Tapi pikirkanlah dari sudut pandang antarmuka.

Jika metode antarmuka menyatakan tipe pengembalian, dan / atau janji tersirat tentang nilai kembali, maka setiap implementasi perlu mengembalikan objek yang diperbarui.

Jadi, Anda dapat bertanya:

  • Bisakah Anda memikirkan kemungkinan implementasi yang tidak ingin repot mengembalikan objek baru?
  • Bisakah Anda memikirkan komponen yang bergantung pada antarmuka (dengan menyuntikkan instance), yang tidak memerlukan karyawan yang diperbarui sebagai nilai pengembalian?

Juga pikirkan benda tiruan untuk pengujian unit. Jelas akan lebih mudah untuk mengejek tanpa nilai balik. Dan komponen dependen lebih mudah untuk diuji, jika dependensinya yang disuntikkan memiliki antarmuka yang lebih sederhana.

Berdasarkan pertimbangan ini Anda dapat membuat keputusan.

Dan jika Anda menyesal nanti, Anda masih bisa memperkenalkan antarmuka kedua, dengan adaptor di antara keduanya. Tentu saja antarmuka tambahan seperti itu meningkatkan kompleksitas komposisi, sehingga semuanya merupakan pertukaran.

donquixote
sumber
0

Pendekatan Anda baik-baik saja. Kekekalan adalah pernyataan yang kuat. Satu-satunya hal yang akan saya tanyakan adalah: Apakah ada tempat lain di mana Anda membangun objek. Jika objek Anda tidak berubah, Anda harus menjawab pertanyaan tambahan karena "Status" diperkenalkan. Dan perubahan keadaan suatu objek dapat terjadi dengan alasan yang berbeda. Maka Anda harus tahu kasus Anda dan tidak boleh berlebihan atau terbagi.

oopexpert
sumber