Merancang Kelas untuk mengambil seluruh kelas sebagai parameter daripada properti individu

30

Katakanlah, misalnya, Anda memiliki aplikasi dengan kelas yang dibagikan secara luas User. Kelas ini memaparkan semua informasi tentang pengguna, ID mereka, nama, tingkat akses ke setiap modul, zona waktu dll.

Data pengguna jelas banyak direferensikan di seluruh sistem, tetapi untuk alasan apa pun, sistem ini diatur sehingga alih-alih mengirimkan objek pengguna ini ke kelas yang bergantung padanya, kami hanya meneruskan properti individual darinya.

Kelas yang membutuhkan id pengguna, hanya akan memerlukan GUID userIdsebagai parameter, kadang-kadang kita mungkin memerlukan nama pengguna juga, sehingga diteruskan sebagai parameter terpisah. Dalam beberapa kasus, ini diteruskan ke metode individual, sehingga nilai tidak dipegang di tingkat kelas sama sekali.

Setiap kali saya membutuhkan akses ke informasi yang berbeda dari kelas Pengguna, saya harus melakukan perubahan dengan menambahkan parameter dan di mana menambahkan kelebihan baru tidak sesuai, saya harus mengubah setiap referensi ke metode atau konstruktor kelas juga.

Pengguna hanyalah satu contoh. Ini banyak dipraktikkan dalam kode kita.

Apakah saya benar dalam berpikir bahwa ini merupakan pelanggaran prinsip Terbuka / Tertutup? Bukan hanya tindakan mengubah kelas yang ada, tetapi mengaturnya di tempat pertama sehingga perubahan luas sangat mungkin diperlukan di masa depan?

Jika kita baru saja melewati Userobjek, saya bisa membuat perubahan kecil ke kelas yang saya kerjakan. Jika saya harus menambahkan parameter, saya mungkin harus membuat lusinan perubahan pada referensi ke kelas.

Apakah ada prinsip lain yang dilanggar oleh praktik ini? Keterbalikan inversi mungkin? Meskipun kami tidak mereferensikan abstraksi, hanya ada satu jenis pengguna, jadi tidak ada kebutuhan nyata untuk memiliki antarmuka pengguna.

Apakah ada prinsip non-SOLID lainnya yang dilanggar, seperti prinsip dasar pemrograman defensif?

Haruskah konstruktor saya terlihat seperti ini:

MyConstructor(GUID userid, String username)

Atau ini:

MyConstructor(User theUser)

Edit Posting:

Disarankan bahwa pertanyaan dijawab dalam "Pass ID atau Object?". Ini tidak menjawab pertanyaan tentang bagaimana keputusan untuk pergi dengan cara apa pun mempengaruhi upaya untuk mengikuti prinsip-prinsip SOLID, yang merupakan inti dari pertanyaan ini.

Jimbo
sumber
11
@gnat: Ini jelas bukan duplikat. Duplikat yang mungkin adalah tentang metode rantai untuk menjangkau jauh ke dalam hierarki objek. Pertanyaan ini sepertinya tidak menanyakan hal itu sama sekali.
Greg Burghardt
2
Bentuk kedua sering digunakan ketika jumlah parameter yang diteruskan telah menjadi berat.
Robert Harvey
12
Satu hal yang saya tidak suka tentang tanda tangan pertama adalah bahwa tidak ada jaminan bahwa userId dan nama pengguna sebenarnya berasal dari pengguna yang sama. Ini adalah bug potensial yang dihindari dengan menyebarkan Pengguna ke mana-mana. Tetapi keputusan benar-benar tergantung pada apa yang disebut metode yang dilakukan dengan argumen.
17 dari 26
9
Kata "parse" tidak masuk akal dalam konteks di mana Anda menggunakannya. Apakah yang Anda maksudkan "lulus" saja?
Konrad Rudolph
5
Bagaimana dengan Idi SOLIDdalamnya? MyConstructorpada dasarnya mengatakan sekarang "Aku butuh a Guiddan a string". Jadi mengapa tidak memiliki antarmuka yang menyediakan a Guiddan a string, biarkan Userimplementasikan antarmuka itu dan biarkan MyConstructorbergantung pada instance yang mengimplementasikan antarmuka itu? Dan jika perlu MyConstructorperubahan, ubah antarmuka. - Ini sangat membantu saya untuk memikirkan antarmuka untuk "milik" konsumen daripada penyedia . Jadi pikirkan "sebagai konsumen saya butuh sesuatu yang melakukan ini dan itu" daripada "sebagai penyedia saya bisa melakukan ini dan itu".
Corak

Jawaban:

31

Sama sekali tidak ada yang salah dengan melewatkan seluruh Userobjek sebagai parameter. Bahkan, mungkin membantu memperjelas kode Anda, dan membuatnya lebih jelas bagi programmer apa metode yang diperlukan jika tanda tangan metode memerlukan a User.

Melewati tipe data sederhana itu bagus, sampai mereka berarti sesuatu selain apa adanya. Pertimbangkan contoh ini:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

Dan contoh penggunaan:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

Bisakah Anda menemukan cacat? Kompiler tidak bisa. "ID pengguna" yang diteruskan hanyalah bilangan bulat. Kami memberi nama variabel usertetapi menginisialisasi nilainya dari blogPostRepositoryobjek, yang mungkin mengembalikan BlogPostobjek, bukan Userobjek - namun kode mengkompilasi dan Anda berakhir dengan kesalahan runtime.

Sekarang perhatikan contoh yang diubah ini:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

Mungkin Barmetode ini hanya menggunakan "ID pengguna" tetapi tanda tangan metode membutuhkan Userobjek. Sekarang mari kita kembali ke contoh penggunaan yang sama seperti sebelumnya, tetapi ubah untuk melewati seluruh "pengguna" di:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

Sekarang kami memiliki kesalahan kompiler. The blogPostRepository.FindMetode mengembalikan BlogPostobjek, yang kita cerdik sebut "pengguna". Kemudian kami meneruskan "pengguna" ini ke Barmetode dan segera mendapatkan kesalahan kompiler, karena kami tidak dapat meneruskan BlogPostke metode yang menerima a User.

Sistem Jenis bahasa sedang ditingkatkan untuk menulis kode yang benar lebih cepat, dan mengidentifikasi cacat pada waktu kompilasi, daripada menjalankan waktu.

Sungguh, harus memperbaiki banyak kode karena perubahan informasi pengguna hanyalah gejala dari masalah lain. Dengan melewati seluruh Userobjek Anda mendapatkan manfaat di atas, selain manfaat tidak harus memperbaiki semua tanda tangan metode yang menerima informasi pengguna ketika ada sesuatu tentang Userkelas yang berubah.

Greg Burghardt
sumber
6
Saya akan mengatakan alasan Anda dengan sendirinya sebenarnya menunjuk ke ladang yang lewat tetapi membuat ladang itu menjadi pembungkus sepele di sekitar nilai sebenarnya. Dalam contoh ini, Pengguna memiliki bidang tipe UserID dan UserID memiliki bidang bernilai integer tunggal. Sekarang deklarasi Bar segera memberi tahu Anda bahwa Bar tidak menggunakan semua informasi tentang Pengguna, hanya ID mereka, tetapi Anda masih tidak dapat membuat kesalahan konyol seperti mengirimkan integer yang tidak datang dari UserID ke Bar.
Ian
(Lanj.) Tentu saja gaya pemrograman semacam ini cukup membosankan, terutama dalam bahasa yang tidak memiliki dukungan sintaksis yang bagus untuk itu (Haskell bagus untuk gaya ini misalnya, karena Anda dapat mencocokkan dengan "ID UserID") .
Ian
5
@Ian: Saya pikir membungkus Id di skate jenisnya sendiri di sekitar masalah asli dibawa oleh OP, yang merupakan perubahan struktural untuk kelas Pengguna membuatnya perlu untuk refactor banyak tanda tangan metode. Melewati seluruh objek Pengguna memecahkan masalah ini.
Greg Burghardt
@Ian: Meskipun harus jujur, bahkan bekerja di C # Saya sudah sangat tergoda untuk membungkus Id dan semacamnya dalam Struct hanya untuk memberikan sedikit lebih banyak kejelasan.
Greg Burghardt
1
"Tidak ada yang salah dengan melewatkan sebuah pointer di tempatnya." Atau referensi, untuk menghindari semua masalah dengan pointer yang bisa Anda temui.
Yay295
17

Apakah saya benar dalam berpikir bahwa ini merupakan pelanggaran prinsip Terbuka / Tertutup?

Tidak, itu bukan pelanggaran prinsip itu. Prinsip itu berkaitan dengan tidak mengubah Usercara yang mempengaruhi bagian lain dari kode yang menggunakannya. Perubahan Anda pada Userbisa menjadi pelanggaran seperti itu, tetapi itu tidak terkait.

Apakah ada prinsip lain yang dilanggar oleh praktik ini? Ketergantungan inversi perahaps?

Tidak. Apa yang Anda gambarkan - hanya menyuntikkan bagian-bagian yang diperlukan dari objek pengguna ke dalam setiap metode - adalah kebalikannya: ini adalah inversi ketergantungan murni.

Apakah ada prinsip non-SOLID lainnya yang dilanggar, seperti prinsip dasar pemrograman defensif?

Tidak. Pendekatan ini adalah cara pengkodean yang benar-benar valid. Itu tidak melanggar prinsip-prinsip seperti itu.

Tetapi inversi ketergantungan hanya merupakan prinsip; itu bukan hukum yang tidak bisa dipatahkan. Dan DI murni dapat menambah kompleksitas ke sistem. Jika Anda menemukan bahwa hanya menyuntikkan nilai-nilai pengguna yang diperlukan ke dalam metode, alih-alih meneruskan seluruh objek pengguna ke dalam metode atau konstruktor, menciptakan masalah, maka jangan lakukan itu. Ini semua tentang mendapatkan keseimbangan antara prinsip dan pragmatisme.

Untuk menanggapi komentar Anda:

Ada masalah dengan tidak perlu menguraikan nilai baru turun lima tingkat rantai dan kemudian mengubah semua referensi ke semua lima metode yang ada ...

Bagian dari masalah di sini adalah Anda jelas tidak menyukai pendekatan ini, sesuai dengan komentar "tidak perlu [lulus] ...". Dan itu cukup adil; tidak ada jawaban yang tepat di sini. Jika Anda merasa itu memberatkan, jangan lakukan itu.

Namun, sehubungan dengan prinsip terbuka / tertutup, jika Anda mengikuti secara ketat maka "... ubah semua referensi ke semua lima metode yang ada ..." adalah indikasi bahwa metode tersebut dimodifikasi, padahal seharusnya tertutup untuk modifikasi. Pada kenyataannya, prinsip terbuka / tertutup masuk akal untuk API publik, tetapi tidak masuk akal untuk internal aplikasi.

... tapi tentu saja rencana untuk mematuhi prinsip itu sejauh dapat dilakukan untuk itu akan mencakup strategi untuk mengurangi kebutuhan akan perubahan di masa depan?

Tapi kemudian Anda berkeliaran di wilayah YAGNI dan itu masih ortogonal dengan prinsip tersebut. Jika Anda memiliki metode Fooyang menggunakan nama pengguna dan kemudian Anda ingin Foomengambil tanggal lahir juga, mengikuti prinsipnya, Anda menambahkan metode baru; Footetap tidak berubah. Sekali lagi itu praktik yang baik untuk API publik, tetapi itu omong kosong untuk kode internal.

Seperti yang disebutkan sebelumnya, ini tentang keseimbangan dan akal sehat untuk situasi apa pun. Jika parameter tersebut sering berubah, maka ya, gunakan Userlangsung. Ini akan menyelamatkan Anda dari perubahan skala besar yang Anda gambarkan. Tetapi jika mereka tidak sering berubah, maka hanya lewat apa yang dibutuhkan adalah pendekatan yang baik juga.

David Arno
sumber
Ada masalah dengan tidak perlu menguraikan nilai baru ke bawah lima tingkat rantai dan kemudian mengubah semua referensi ke semua lima metode yang ada. Mengapa prinsip Open / Closed hanya berlaku untuk kelas Pengguna dan tidak juga untuk kelas yang saat ini saya edit, yang juga dikonsumsi oleh kelas lain? Saya sadar bahwa prinsipnya secara spesifik tentang menghindari perubahan, tetapi tentu saja rencana untuk mematuhi prinsip itu sejauh yang dapat dilakukan untuk melakukan hal itu akan termasuk strategi untuk mengurangi kebutuhan akan perubahan di masa depan?
Jimbo
@Jimbo, saya telah memperbarui jawaban saya untuk mencoba dan menanggapi komentar Anda.
David Arno
Saya menghargai kontribusi Anda. BTW. Bahkan Robert C Martin tidak menerima prinsip Terbuka / Tertutup memiliki aturan keras. Ini adalah aturan praktis yang pasti akan dilanggar. Menerapkan prinsip adalah latihan dalam berusaha untuk mematuhinya sebanyak mungkin. Itulah sebabnya saya menggunakan kata "praktis" sebelumnya.
Jimbo
Ini bukan ketergantungan ketergantungan untuk melewati parameter Pengguna daripada Pengguna itu sendiri.
James Ellis-Jones
@ JamesEllis-Jones, Ketergantungan invert membalik ketergantungan dari "minta", untuk "kirim". Jika Anda memberikan Usercontoh dan kemudian meminta objek itu untuk mendapatkan parameter, maka Anda hanya membalik sebagian dependensi; masih ada yang bertanya. Inversi ketergantungan sebenarnya adalah 100% "katakan, jangan tanya". Tapi itu datang dengan harga yang rumit.
David Arno
10

Ya, mengubah fungsi yang ada merupakan pelanggaran terhadap Prinsip Terbuka / Tertutup. Anda sedang memodifikasi sesuatu yang harus ditutup untuk modifikasi karena perubahan persyaratan. Desain yang lebih baik (untuk tidak berubah ketika persyaratan berubah) adalah untuk meneruskan Pengguna ke hal-hal yang seharusnya berfungsi pada pengguna.

Tapi itu mungkin bertabrakan dengan Segregasi Prinsip Interface, karena Anda bisa melewati sepanjang jalan informasi lebih dari kebutuhan fungsi untuk melakukan tugasnya.

Jadi, seperti kebanyakan hal - itu tergantung .

Dengan hanya menggunakan nama pengguna, mari fungsi ini menjadi lebih fleksibel, bekerja dengan nama pengguna terlepas dari mana mereka berasal dan tanpa perlu membuat objek Pengguna yang berfungsi penuh. Ini memberikan ketahanan untuk berubah jika Anda berpikir bahwa sumber data akan berubah.

Menggunakan seluruh Pengguna membuatnya lebih jelas tentang penggunaan dan membuat kontrak lebih kuat dengan peneleponnya. Ini memberikan ketahanan untuk berubah jika Anda berpikir bahwa lebih banyak Pengguna akan dibutuhkan.

Telastyn
sumber
+1 tetapi saya tidak yakin tentang frasa Anda "Anda bisa menyampaikan informasi lebih lanjut". Ketika Anda melewati (Pengguna pengguna) Anda melewati informasi yang sangat minimum, referensi ke satu objek. Benar bahwa referensi dapat digunakan untuk mendapatkan informasi lebih lanjut, tetapi itu berarti kode panggilan tidak harus mendapatkannya. Saat Anda melewati (GUID userid, string username) metode yang dipanggil selalu dapat memanggil User.find (userid) untuk menemukan antarmuka publik objek, sehingga Anda tidak benar-benar menyembunyikan apa pun.
dcorking
5
@dcorking, " Ketika Anda lulus (Pengguna pengguna) Anda melewati informasi yang sangat minim, referensi ke satu objek ". Anda melewatkan informasi maksimum yang terkait dengan objek itu: seluruh objek. " Metode yang dipanggil selalu dapat memanggil User.find (userid) ...". Dalam sistem yang dirancang dengan baik, itu tidak akan mungkin karena metode yang dimaksud tidak akan memiliki akses User.find(). Bahkan ada bahkan tidak harus menjadi seorang User.find. Menemukan pengguna tidak boleh menjadi tanggung jawab User.
David Arno
2
@dcorking - tingkatkan. Bahwa Anda melewati referensi yang kebetulan kecil adalah kebetulan teknis. Anda menggabungkan seluruh Userfungsi. Mungkin itu masuk akal. Tapi mungkin fungsinya hanya peduli dengan nama pengguna - dan menyampaikan hal-hal seperti tanggal bergabung Pengguna, atau alamat tidak patut.
Telastyn
@ DavidVrno mungkin itu adalah kunci untuk jawaban yang jelas untuk OP. Tanggung jawab siapa yang harus menjadi Pengguna? Apakah ada nama untuk prinsip desain memisahkan finder / pabrik dari kelas?
dcorking
1
@dcorking Saya akan mengatakan itu adalah salah satu implikasi dari Prinsip Tanggung Jawab Tunggal. Mengetahui di mana Pengguna disimpan dan cara mengambilnya dengan ID adalah tanggung jawab terpisah yang Userseharusnya tidak dimiliki kelas. Mungkin ada UserRepositoryatau serupa yang berhubungan dengan hal-hal seperti itu.
Hulk
3

Desain ini mengikuti Pola Objek Parameter . Ini memecahkan masalah yang timbul dari memiliki banyak parameter dalam tanda tangan metode.

Apakah saya benar dalam berpikir bahwa ini merupakan pelanggaran prinsip Terbuka / Tertutup?

Tidak. Menerapkan pola ini memungkinkan prinsip Buka / tutup (OCP). Misalnya kelas turunan dariUser dapat diberikan sebagai parameter yang menginduksi perilaku yang berbeda di kelas konsumsi.

Apakah ada prinsip lain yang dilanggar oleh praktik ini?

Itu bisa terjadi. Izinkan saya menjelaskan berdasarkan prinsip-prinsip SOLID.

The prinsip tanggung jawab Tunggal (SRP) dapat dilanggar jika memiliki desain seperti yang Anda telah dijelaskan:

Kelas ini memaparkan semua informasi tentang pengguna, ID mereka, nama, tingkat akses ke setiap modul, zona waktu dll.

Masalahnya dengan semua informasi . Jika Userkelas memiliki banyak properti, itu menjadi Obyek Transfer Data besar yang mengangkut informasi yang tidak terkait dari perspektif kelas konsumsi. Contoh: Dari perspektif kelas konsumen UserAuthentication, properti User.IddanUser.Name relevan, tetapi tidak User.Timezone.

The Antarmuka segregasi Prinsip (ISP) juga melanggar dengan alasan serupa tetapi menambahkan perspektif lain. Contoh: Misalkan kelas konsumsi UserManagementmembutuhkan properti User.Nameuntuk dipecah menjadi User.LastNamedan User.FirstNamekelasUserAuthentication juga harus dimodifikasi untuk ini.

Untungnya ISP juga memberi Anda kemungkinan jalan keluar dari masalah: Biasanya Objek Parameter atau Objek Transport Data mulai kecil dan tumbuh seiring waktu. Jika hal ini menjadi sulit, pertimbangkan pendekatan berikut: Memperkenalkan antarmuka yang disesuaikan dengan kebutuhan kelas konsumen. Contoh: Perkenalkan antarmuka dan biarkan Userkelas berasal darinya:

class User : IUserAuthenticationInfo, IUserLocationInfo { ... }

Setiap antarmuka harus mengekspos subset properti terkait dari Userkelas yang diperlukan untuk kelas konsumsi untuk memenuhi operasinya. Cari kelompok properti. Cobalah untuk menggunakan kembali antarmuka. Dalam hal UserAuthenticationpenggunaan kelas konsumsi IUserAuthenticationInfobukan User. Kemudian jika mungkin memecah Userkelas menjadi beberapa kelas beton menggunakan antarmuka sebagai "stensil".

Theo Lenndorff
sumber
1
Setelah Pengguna menjadi rumit, ada ledakan kombinatorial dari kemungkinan subinterfaces misalnya, jika Pengguna hanya memiliki 3 properti, ada 7 kemungkinan kombinasi. Proposal Anda kedengarannya bagus tetapi tidak bisa dijalankan.
user949300
1. Secara analitis Anda benar. Namun tergantung pada bagaimana domain dimodelkan, bit informasi terkait cenderung berkelompok. Jadi praktis tidak perlu berurusan dengan semua kemungkinan kombinasi antarmuka dan properti. 2. Pendekatan yang diuraikan tidak dimaksudkan sebagai solusi universal, tetapi mungkin saya harus menambahkan beberapa 'mungkin' dan 'dapat' ke dalam jawabannya.
Theo Lenndorff
2

Ketika dihadapkan dengan masalah ini dalam kode saya sendiri, saya telah menyimpulkan bahwa model kelas dasar / objek adalah jawabannya.

Contoh umum adalah pola repositori. Seringkali ketika menanyakan database melalui repositori, banyak metode dalam repositori mengambil banyak parameter yang sama.

Aturan praktis saya untuk repositori adalah:

  • Di mana lebih dari satu metode mengambil 2 parameter yang sama atau lebih, parameter harus dikelompokkan bersama sebagai objek model.

  • Di mana metode mengambil lebih dari 2 parameter, parameter harus dikelompokkan bersama sebagai objek model.

  • Model dapat mewarisi dari basis bersama, tetapi hanya ketika itu benar-benar masuk akal (biasanya lebih baik untuk refactor lebih lambat daripada memulai dengan pewarisan dalam pikiran).


Masalah dengan menggunakan model dari lapisan / area lain tidak menjadi jelas sampai proyek mulai menjadi sedikit rumit. Hanya saat itulah Anda menemukan lebih sedikit kode yang menciptakan lebih banyak pekerjaan atau lebih banyak komplikasi.

Dan ya, benar-benar baik untuk memiliki 2 model berbeda dengan properti identik yang melayani berbagai lapisan / tujuan (mis. ViewModels vs POCOs).

Dom
sumber
2

Mari kita periksa aspek individual SOLID:

  • Tanggung jawab tunggal: mungkin berlarut-larut jika orang cenderung melewati hanya bagian-bagian kelas.
  • Buka / tutup: Tidak relevan di mana bagian kelas diedarkan, hanya di mana objek penuh diedarkan. (Saya pikir disitulah disonansi kognitif muncul: Anda perlu mengubah kode yang jauh tetapi kelas itu sendiri tampaknya baik-baik saja.)
  • Substitusi Liskov: Non-isu, kami tidak melakukan subkelas.
  • Pembalikan ketergantungan (tergantung pada abstraksi, bukan data konkret). Ya itu dilanggar: Orang tidak punya abstraksi, mereka mengambil elemen konkret dari kelas dan menyebarkannya. Saya pikir itu masalah utama di sini.

Satu hal yang cenderung membingungkan insting desain adalah bahwa kelas pada dasarnya untuk objek global, dan pada dasarnya hanya-baca. Dalam situasi seperti itu, melanggar abstraksi tidak banyak merugikan: Hanya membaca data yang tidak dimodifikasi menciptakan kopling yang sangat lemah; hanya ketika itu menjadi tumpukan besar rasa sakit menjadi nyata.
Untuk mengembalikan insting desain, anggap saja objeknya tidak terlalu global. Konteks apa yang dibutuhkan suatu fungsi jika Userobjek dapat dimutasi kapan saja? Komponen objek apa yang kemungkinan akan bermutasi bersama? Ini dapat dipisahkanUser , apakah sebagai sub-objek yang direferensikan atau sebagai antarmuka yang memaparkan hanya "potongan" bidang terkait tidak begitu penting.

Prinsip lain: Lihatlah fungsi yang menggunakan bagian User dan lihat bidang (atribut) mana yang cenderung disatukan. Itu daftar awal sub-proyek yang bagus - Anda pasti perlu memikirkan apakah mereka benar-benar milik bersama.

Ini banyak pekerjaan, dan agak sulit untuk dilakukan, dan kode Anda akan menjadi sedikit kurang fleksibel karena akan menjadi sedikit lebih sulit untuk mengidentifikasi subobject (subinterface) yang perlu diteruskan ke suatu fungsi, terutama jika subobject memiliki tumpang tindih.

Berpisah Usersebenarnya akan menjadi jelek jika subobjek tumpang tindih, maka orang akan bingung tentang mana yang harus dipilih jika semua bidang yang diperlukan berasal dari tumpang tindih. Jika Anda berpisah secara hierarkis (mis. Anda memiliki UserMarketSegmentyang, antara lain, miliki UserLocation), orang tidak akan yakin pada level apa fungsi yang mereka tulis: apakah berurusan dengan data pengguna di Location tingkat atau diMarketSegment level? Tidak persis membantu bahwa ini dapat berubah dari waktu ke waktu, yaitu Anda kembali pada perubahan tanda tangan fungsi, kadang-kadang di seluruh rantai panggilan.

Dengan kata lain: Kecuali Anda benar-benar tahu domain Anda dan memiliki ide yang cukup jelas tentang modul apa yang berhubungan dengan aspek apa User, tidak benar-benar layak untuk memperbaiki struktur program.

Toolforger
sumber
1

Ini pertanyaan yang sangat menarik. Itu tergantung.

Jika Anda berpikir metode Anda dapat berubah di masa mendatang secara internal untuk memerlukan parameter objek Pengguna yang berbeda, Anda tentu harus menyampaikan semuanya. Keuntungannya adalah bahwa kode eksternal untuk metode ini kemudian dilindungi dari perubahan dalam metode dalam hal parameter apa yang digunakannya, yang seperti yang Anda katakan akan menyebabkan kaskade perubahan secara eksternal. Jadi mengirimkan seluruh Pengguna meningkatkan enkapsulasi.

Jika Anda cukup yakin Anda tidak akan perlu menggunakan apa pun selain mengatakan email Pengguna, Anda harus meneruskannya. Keuntungannya adalah Anda kemudian dapat menggunakan metode ini dalam konteks yang lebih luas: Anda bisa menggunakannya misalnya dengan email Perusahaan atau email yang baru saja diketik seseorang. Ini meningkatkan fleksibilitas.

Ini adalah bagian dari serangkaian pertanyaan yang lebih luas tentang membangun kelas untuk memiliki cakupan yang luas atau sempit, termasuk apakah akan menyuntikkan dependensi atau tidak dan apakah memiliki objek yang tersedia secara global. Ada kecenderungan yang tidak menguntungkan saat ini untuk berpikir bahwa ruang lingkup yang lebih sempit selalu baik. Namun, selalu ada tradeoff antara enkapsulasi dan fleksibilitas seperti dalam kasus ini.

James Ellis-Jones
sumber
1

Saya menemukan yang terbaik untuk melewati parameter sesedikit mungkin dan sebanyak yang diperlukan. Ini membuat pengujian lebih mudah dan tidak membutuhkan kisi seluruh objek.

Dalam contoh Anda, jika Anda hanya akan menggunakan user-id atau nama pengguna maka ini semua yang harus Anda lewati. Jika pola ini berulang beberapa kali dan objek pengguna yang sebenarnya jauh lebih besar maka saran saya adalah membuat antarmuka yang lebih kecil untuk itu. Bisa jadi

interface IIdentifieable
{
    Guid ID { get; }
}

atau

interface INameable
{
    string Name { get; }
}

Ini membuat pengujian dengan mengejek jauh lebih mudah dan Anda langsung tahu nilai mana yang benar-benar digunakan. Kalau tidak, Anda sering perlu menginisialisasi objek kompleks dengan banyak dependensi lain meskipun pada akhirnya Anda hanya perlu satu atau dua properti.

t3chb0t
sumber
1

Inilah sesuatu yang saya temui dari waktu ke waktu:

  • Metode mengambil argumen tipe User(atau Productatau apa pun) yang memiliki banyak properti, meskipun metode hanya menggunakan beberapa dari mereka.
  • Untuk beberapa alasan, beberapa bagian dari kode perlu memanggil metode itu walaupun tidak memiliki objek yang terisi penuh User. Itu menciptakan sebuah instance dan menginisialisasi hanya properti metode yang sebenarnya dibutuhkan.
  • Ini terjadi beberapa kali.
  • Setelah beberapa saat, ketika Anda menemukan metode yang memiliki Userargumen, Anda harus menemukan panggilan ke metode itu untuk menemukan dari mana Userasalnya sehingga Anda tahu properti mana yang terisi. Apakah itu pengguna "nyata" dengan alamat email, atau hanya dibuat untuk melewati ID pengguna dan beberapa izin?

Jika Anda membuat Userdan hanya mengisi beberapa properti karena itulah yang dibutuhkan metode, maka penelepon benar-benar tahu lebih banyak tentang cara kerja dalam metode daripada yang seharusnya.

Lebih buruk lagi, ketika Anda memiliki instance User, Anda harus tahu dari mana asalnya sehingga Anda tahu properti mana yang dihuni. Anda tidak mau harus tahu itu.

Seiring waktu, ketika pengembang melihat Userdigunakan sebagai wadah untuk argumen metode, mereka mungkin mulai menambahkan properti ke dalamnya untuk skenario sekali pakai. Sekarang semakin jelek, karena kelas semakin berantakan dengan properti yang hampir selalu nol atau default.

Korupsi semacam itu tidak bisa dihindari, tetapi terjadi berulang kali ketika kita melewati objek hanya karena kita membutuhkan akses ke beberapa propertinya. Zona bahaya adalah pertama kalinya Anda melihat seseorang membuat instance Userdan hanya mengisi beberapa properti sehingga mereka dapat meneruskannya ke suatu metode. Letakkan kaki Anda di atasnya karena itu jalan yang gelap.

Jika memungkinkan, berikan contoh yang tepat untuk pengembang berikutnya dengan hanya memberikan apa yang Anda harus lulus.

Scott Hannen
sumber