Pelanggaran Prinsip Tanggung Jawab Tunggal?

8

Saya baru-baru ini berdebat dengan pengembang lain mengenai kelas di bawah ini:

public class GroupBillingPayment
{
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;
        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        UpdateGroupBilling([Parameters])
    }
}

Saya percaya UpdateGroupBillingtidak boleh dipanggil di dalam metode save karena melanggar prinsip tanggung jawab tunggal. Namun, ia mengatakan bahwa setiap kali pembayaran dilakukan, tagihan harus diperbarui. Karenanya inilah pendekatan yang tepat.

Pertanyaan saya, apakah SRP dilanggar di sini? Jika ya, bagaimana kita bisa memperbaiki itu agar kedua kriteria kita terpenuhi?

gvk
sumber
6
Ini tergantung pada domain masalah dan arsitektur Anda. Bagaimana seharusnya data dan acara mengalir melalui aplikasi Anda? Apa tanggung jawab kelas yang terlibat? Tidak ada konteks yang cukup dalam pertanyaan Anda untuk menjawab ini secara objektif.
amon
7
SRP bukan pedoman bebas konteks.
whatsisname
1
Versi yang diperbarui memiliki beberapa masalah yang di luar topik di sini. Anda dapat mempertimbangkan mempostingnya di codereview.stackexchange.com untuk mendapatkan beberapa saran untuk perbaikan ...
Timothy Truckle
1
Terapkan POAP ! Jika Anda tidak mengerti mengapa prinsip itu ada dan bagaimana itu berlaku untuk situasi Anda, itu belum layak diperdebatkan. Dan ketika Anda benar- benar memahami tujuan dari prinsip tersebut, jawabannya akan jauh lebih mudah .... bagi Anda. Bukan untuk kita. Kami tidak tahu basis kode Anda, organisasi Anda, atau jenis kacang tertentu yang bisa Anda gunakan. ... Saya VTC-ing ini. Saya tidak berpikir kami tidak bisa memberikan jawaban yang "benar" kepada Anda. Kecuali jika jawabannya adalah: Anda perlu memahami prinsipnya sebelum Anda memperdebatkannya.
svidgen
2
Anda hampir pasti terlalu memikirkan ini. Jika persyaratan bisnis menentukan bahwa Anda melakukan semacam pembersihan atau pembaruan sebagai bagian dari suatu proses, maka pembersihan atau pembaruan tersebut merupakan bagian dari tanggung jawab, dan oleh karena itu tanggung jawabnya masih tunggal.
Robert Harvey

Jawaban:

6

Saya akan melihatnya seperti ini:

Suatu metode harus memanggil metode lain (dalam objek yang sama atau lainnya) yang membuatnya menjadi metode yang dikomposisikan atau melakukan "perhitungan primitif" (tingkat abstraksi yang sama).

Tanggung jawab metode yang dikomposisikan adalah "panggil metode lain".

Jadi jika Savemetode Anda melakukan beberapa "perhitungan primitif" itu sendiri (mis. Memeriksa nilai pengembalian), SPR mungkin dilanggar. Jika "perhitungan primitif" ini hidup dalam metode lain yang disebut dengan Savemetode Anda SRP tidak dilanggar.


Versi terbaru dari Savemetode ini tidak mengikuti prinsip lapisan abstraksi tunggal . Karena ini adalah masalah yang lebih penting, Anda harus mengekstraknya ke metode baru.

Ini akan dikonversi Savemenjadi metode yang dikomposisikan . Seperti yang tertulis, tanggung jawab metode yang dikomposisikan adalah "panggil metode lain". Oleh karena itu menelepon di UpdateGroupBilling([Parameters])sini bukan merupakan pelanggaran terhadap SRP, tetapi keputusan kasus bisnis.

Timothy Truckle
sumber
5
+1, tepatnya. Prinsip Tanggung Jawab Tunggal tidak berarti bahwa Anda hanya dapat melakukan satu hal - itu akan membuat pasangan tidak mungkin sama sekali . Anda hanya harus melakukan satu hal pada level abstraksi Anda saat ini .
Kilian Foth
Savemetode telah diperbarui. Silakan lihat apakah ini membantu
gvk
2

Tanggung jawab tunggal dapat diartikan sebagai fungsi / kelas harus hanya memiliki satu alasan untuk berubah.

Jadi Savemetode Anda saat ini akan melanggar prinsip itu, karena itu harus diubah oleh lebih dari satu alasan.
1. Simpan logika berubah
2. Jika Anda memutuskan untuk memperbarui sesuatu yang lain selain memperbarui grup penagihan

Anda dapat refactor dengan memperkenalkan SaveModelmetode yang hanya akan bertanggung jawab untuk menyimpan. Dan memperkenalkan metode lain yang menggabungkan semua operasi yang diperlukan berdasarkan kebutuhan Anda. Jadi Anda berakhir dengan dua metode

public void SaveModel(IGroupBillingPayment model)
{
    // only saves model
}

public void Save(IGroupBillingPayment model)
{
    SaveModel(model);
    UpdateGroupBilling([Parameters]);
}

Di mana SaveModelmetode akan memiliki tanggung jawab untuk menyimpan model ke database dan satu alasan untuk berubah - jika menyimpan "logika" akan berubah.

Save metode sekarang memiliki tanggung jawab untuk memanggil semua metode yang diperlukan untuk proses "tabungan" penuh, dan memiliki satu alasan untuk berubah - jika jumlah metode yang diperlukan akan berubah.

Saya pikir validasi dapat dipindahkan ke luar SaveModeljuga.

Fabio
sumber
0

Single Responsibility adalah IMHO konsep yang tidak mudah untuk dipecahkan.

Aturan praktis yang sederhana adalah:

Ketika saya harus menjelaskan, apa yang dilakukan oleh metode / kelas, dan harus menggunakan kata »dan«, itu adalah indikator, bahwa sesuatu yang berbau mungkin sedang terjadi .

Di satu sisi, itu hanya indikator dan di sisi lain itu bekerja lebih baik terbalik: Jika dua hal terjadi, Anda tidak dapat menghindari menggunakan kata »dan« dan karena Anda melakukan dua hal, Anda melanggar SRP .

Tetapi, apakah itu berarti di sisi lain, jika Anda melakukan lebih dari satu hal, Anda melanggar SRP ? Tidak. Karena jika tidak, Anda terbatas pada kode sepele dan masalah sepele untuk dipecahkan. Anda akan melukai diri sendiri jika interpretasi Anda terlalu ketat.

Perspektif lain tentang SRP adalah: satu tingkat abstraksi . Selama Anda berurusan dengan satu tingkat abstraksi, Anda sebagian besar baik-baik saja.

Apa arti semua itu untuk pertanyaan Anda:

Saya percaya UpdateGroupBilling tidak boleh dipanggil di dalam metode save karena melanggar prinsip tanggung jawab tunggal. Namun, ia mengatakan bahwa setiap kali pembayaran dilakukan, tagihan harus diperbarui. Karenanya inilah pendekatan yang tepat.

Untuk memutuskan, apakah ini merupakan pelanggaran terhadap SRP , perlu diketahui, apa yang terjadi di save()-Metode. Jika metode ini -seperti nama-menyarankan bertanggung jawab untuk bertahan model untuk database, panggilan untuk UpdateGroupBilling adalah IMHO pelanggaran SRP , karena Anda memperluas konteks »menghemat pembayaran«. Anda akan memparafrasekannya dengan »Saya menyimpan pembayaran dan memperbarui tagihan grup«, yang - seperti yang saya katakan sebelumnya - merupakan indikator (kemungkinan) pelanggaran SRP .

Di sisi lain, jika metode ini menggambarkan "resep-pembayaran" -yaitu langkah-langkah apa yang harus diambil dalam proses- dan memutuskan: setiap kali pembayaran selesai, itu harus disimpan (ditangani di tempat lain) dan kemudian penagihan grup harus diperbarui (ditangani di tempat lain), itu bukan pelanggaran SRP, karena Anda tidak meninggalkan abstraksi "resep": Anda membedakan antara apa yang harus dilakukan (dalam resep) dan di mana itu dilakukan (di kelas / metode / modul yang sesuai). Tetapi jika itu yang dilakukan oleh save()-metode Anda (menjelaskan langkah mana yang harus diambil), Anda harus mengganti nama.

Tanpa konteks lebih lanjut, sulit untuk mengatakan sesuatu yang konkret.

Edit: Anda memperbarui pos inital Anda. Saya akan mengatakan, bahwa metode ini melanggar SRP dan harus di refactored. The pengambilan data harus menjadi faktor keluar (itu harus menjadi parameter dari metode ini). The penambahan data (updateBy / On) harus dilakukan di tempat lain. The penghematan harus dilakukan di tempat lain. Maka akan baik-baik saja untuk pergi UpdateGroupBilling([Parameters])apa adanya.

Thomas Junk
sumber
0

Sulit untuk memastikan tanpa konteks penuh tetapi saya pikir intuisi Anda benar. Indikator SRP favorit pribadi saya adalah apakah Anda akan tahu persis ke mana harus pergi dan mengubah sesuatu untuk memodifikasi fitur beberapa bulan kemudian.

Kelas yang mendefinisikan jenis pembayaran bukanlah tempat yang saya harapkan untuk mendefinisikan kembali siapa yang membayar atau bagaimana pembayaran dilakukan. Saya berharap itu menjadi salah satu dari banyak jenis pembayaran yang hanya memberikan detail penting yang digunakan beberapa bagian lain dari aplikasi untuk memulai, melakukan, atau memutar kembali / membatalkan transaksi, mengumpulkan detail tersebut melalui antarmuka yang seragam.

Ini juga terlihat seperti resep untuk pelanggaran prinsip KERING yang lebih umum jika Anda akan memiliki beberapa jenis masing-masing memilah transaksi mereka sendiri menggunakan banyak metode yang sama. SOLID tidak selalu merasa 100% relevan dengan pengembang JavaScript (saya kira banyak hal itu dijelaskan dengan buruk), tetapi KERING adalah standar utama dalam metodologi pemrograman umum IMO. Setiap kali saya menemukan ide yang terasa seperti perpanjangan KERING, saya memberikannya pertimbangan. SRP adalah salah satunya. Jika semua orang tetap pada topik, Anda cenderung tergoda untuk mengulangi sendiri.

Erik Reppen
sumber
0

Jika hanya ada satu cara untuk secara konseptual UpdateGroupBilling(...), dan itu hanya pernah terjadi di tempat itu, maka mungkin tidak masalah di mana itu berada. Tetapi pertanyaannya terkait dengan konsep, bukan dengan keadaan duniawi, yaitu "untuk saat ini kita hanya melakukannya di sini."

Jika tidak demikian, maka salah satu cara untuk memperbaikinya adalah dengan menggunakan Terbitkan / Berlangganan dan beri tahu setiap kali pembayaran disimpan, dan minta penagihan berlangganan acara itu dan perbarui entri yang relevan. Ini bagus terutama jika Anda perlu memiliki beberapa jenis penagihan yang perlu diperbarui. Metode lain adalah dengan memikirkan Penagihan sebagai pola strategi, yaitu memilih satu dan menggunakannya, atau menerimanya serta parameter. Atau, jika penagihan tidak selalu terjadi, Anda dapat menambahkannya sebagai dekorator, dll, dll. Tetapi sekali lagi, ini tergantung pada model konseptual Anda dan bagaimana Anda ingin memikirkannya, jadi Anda harus mencari tahu .

Namun, satu hal penting yang perlu dipertimbangkan adalah penanganan kesalahan. Jika penagihan gagal, apa yang terjadi dengan operasi sebelumnya?

Yam Marcovic
sumber
0

Saya pikir desain ini melanggar SRP, tetapi sangat mudah untuk memperbaikinya dan masih melakukan segala hal lain yang diperlukan.

Pikirkan tentang pesan dan tentang apa yang terjadi dalam metode ini. Seharusnya menyimpan GroupBillingPayment, tetapi tidak ada yang salah jika GroupBillingPayment memberi tahu kelas bahwa itu telah disimpan. Terutama jika menerapkan pola yang secara eksplisit memperlihatkan perilaku itu.

Anda bisa menggunakan pola Pengamat .

Berikut ini contoh cara kerjanya dalam kasus Anda:

public interface Subject {

    public void register(Observer observer);
    public void unregister(Observer observer);

    public void notifyObservers();      
}

public class GroupBillingPayment implements Subject {

    private List<Observer> observers;
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;

        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        //UpdateGroupBilling([Parameters]) The Observer will have this responsability instead now
        notifyObservers();
    }

    public void notifyObservers() {
        for (Observer obj : observers) {
            obj.update();
        }
    }
}

public interface Observer {     
    public void update();

    public void setSubject(Subject sub);
}

Sekarang yang harus Anda lakukan adalah membuat satu atau lebih Observersyang akan terikat GroupBillingPaymentdan dengan demikian diberitahu setiap kali itu disimpan. Itu membuat dependensinya sendiri, tidak tahu apa yang diberitahukan sehingga tidak bergantung pada ini sama sekali.

Steve Chamaillard
sumber
0

Anda ingin mencapai X. Kecuali X cukup sepele, Anda menulis metode yang akan melakukan segalanya untuk mencapai X, panggil metode "reachX", dan panggil metode itu. Tanggung jawab tunggal "mencapai X" adalah melakukan segalanya untuk mencapai X. "Mencapai X" tidak boleh melakukan hal lain.

Jika X rumit, maka banyak tindakan mungkin diperlukan untuk mencapai X. Serangkaian tindakan yang diperlukan dapat berubah dari waktu ke waktu, jadi ketika itu terjadi, Anda mengubah metode mencapai X, tetapi jika semuanya berjalan dengan baik, Anda masih bisa menyebutnya begitu.

Dalam contoh Anda, metode "Simpan" tanggung jawab bukan untuk menyimpan tagihan dan memperbarui tagihan grup (dua tanggung jawab), tanggung jawabnya adalah untuk mengambil semua tindakan yang diperlukan untuk membuat tagihan dicatat secara permanen. Satu tanggung jawab. Mungkin Anda menamainya dengan buruk.

gnasher729
sumber
-2

Jika saya mengerti maksud Anda, saya akan memiliki pembayaran untuk meningkatkan acara seperti "Pembayaran dilakukan" dan kelas yang menangani penagihan yang berlangganan. Dengan cara ini penangan pembayaran tidak tahu apa-apa tentang penagihan.

Zalomon
sumber