Saya mencoba membuat program untuk mengelola karyawan. Saya tidak bisa, bagaimanapun, mencari tahu bagaimana merancang Employee
kelas. Tujuan saya adalah untuk dapat membuat dan memanipulasi data karyawan pada database menggunakan suatu Employee
objek.
Implementasi dasar yang saya pikirkan, adalah yang sederhana ini:
class Employee
{
// Employee data (let's say, dozens of properties).
Employee() {}
Create() {}
Update() {}
Delete() {}
}
Menggunakan implementasi ini, saya mengalami beberapa masalah.
- The
ID
seorang karyawan diberikan oleh database, jadi jika saya menggunakan objek untuk menggambarkan seorang karyawan baru, tidak akan adaID
untuk menyimpan lagi, sementara objek yang mewakili karyawan yang ada akan memilikiID
. Jadi saya punya properti yang kadang-kadang menggambarkan objek dan kadang tidak (Yang bisa menunjukkan bahwa kita melanggar SRP ? Karena kita menggunakan kelas yang sama untuk mewakili karyawan baru dan yang sudah ada ...). - The
Create
Metode seharusnya membuat seorang karyawan pada database, sementaraUpdate
danDelete
seharusnya bertindak atas seorang karyawan yang ada (Sekali lagi, SRP ...). - Parameter apa yang harus dimiliki oleh metode 'Buat'? Puluhan parameter untuk semua data karyawan atau mungkin suatu
Employee
objek? - Haruskah kelas tidak berubah?
- Bagaimana cara
Update
kerjanya? Apakah akan mengambil properti dan memperbarui database? Atau mungkin itu akan mengambil dua objek - yang "lama" dan yang "baru", dan memperbarui database dengan perbedaan di antara mereka? (Saya pikir jawabannya ada hubungannya dengan jawaban tentang mutabilitas kelas). - Apa yang akan menjadi tanggung jawab konstruktor? Apa yang akan menjadi parameter yang dibutuhkan? Apakah itu mengambil data karyawan dari database menggunakan
id
parameter dan mereka mengisi properti?
Jadi, seperti yang Anda lihat, saya memiliki sedikit kekacauan di kepala saya, dan saya sangat bingung. Bisakah Anda membantu saya memahami bagaimana seharusnya kelas seperti itu?
Harap perhatikan bahwa saya tidak ingin pendapat, hanya untuk memahami bagaimana kelas yang sering digunakan umumnya dirancang.
Employee
objek untuk memberikan abstraksi, pertanyaan 4. dan 5. umumnya tidak dapat dijawab, tergantung pada kebutuhan Anda, dan jika Anda memisahkan struktur dan operasi CRUD menjadi dua kelas, maka itu cukup jelas, konstruktorEmployee
tidak dapat mengambil data dari db lagi, jadi itu jawaban 6.Update
seorang karyawan, atau apakah Anda memperbarui catatan karyawan? Apakah AndaEmployee.Delete()
, atau tidakBoss.Fire(employee)
?Jawaban:
Ini adalah transkripsi yang lebih baik dari komentar awal saya di bawah pertanyaan Anda. Jawaban atas pertanyaan yang diajukan oleh OP dapat ditemukan di bagian bawah jawaban ini. Periksa juga catatan penting yang terletak di tempat yang sama.
Apa yang saat ini Anda gambarkan, Sipo, adalah pola desain yang disebut Rekaman aktif . Seperti halnya segala sesuatu, bahkan yang ini telah menemukan tempatnya di antara programmer, tetapi telah dibuang demi repositori dan pola data mapper karena satu alasan sederhana, skalabilitas.
Singkatnya, catatan aktif adalah objek, yang:
Anda mengatasi beberapa masalah dengan desain Anda saat ini dan masalah utama desain Anda dibahas pada poin terakhir, ke-6 (terakhir tapi tidak kalah penting, saya kira). Ketika Anda memiliki kelas yang Anda merancang konstruktor dan Anda bahkan tidak tahu apa yang harus dilakukan konstruktor, kelas mungkin melakukan sesuatu yang salah. Itu terjadi dalam kasus Anda.
Tetapi memperbaiki desain sebenarnya cukup sederhana dengan memecah representasi entitas dan logika CRUD menjadi dua (atau lebih) kelas.
Seperti inilah desain Anda sekarang:
Employee
- berisi informasi tentang struktur karyawan (atributnya) dan metode bagaimana memodifikasi entitas (jika Anda memutuskan untuk pergi dengan cara yang bisa berubah), berisi logika CRUD untukEmployee
entitas, dapat mengembalikan daftarEmployee
objek, menerimaEmployee
objek saat Anda ingin memperbarui karyawan, dapat mengembalikan satuEmployee
melalui metode sepertigetSingleById(id : string) : Employee
Wow, kelasnya tampak besar.
Ini akan menjadi solusi yang diusulkan:
Employee
- berisi informasi tentang struktur karyawan (atributnya) dan metode bagaimana memodifikasi entitas (jika Anda memutuskan untuk pergi dengan cara yang bisa berubah)EmployeeRepository
- berisi logika CRUD untukEmployee
entitas, dapat mengembalikan daftarEmployee
objek, menerimaEmployee
objek saat Anda ingin memperbarui karyawan, dapat mengembalikan satuEmployee
melalui metode sepertigetSingleById(id : string) : Employee
Pernahkah Anda mendengar tentang pemisahan kekhawatiran ? Tidak, sekarang akan. Ini adalah versi Prinsip Tanggung Jawab Tunggal yang tidak terlalu ketat, yang mengatakan bahwa kelas seharusnya hanya memiliki satu tanggung jawab, atau seperti yang dikatakan Paman Bob:
Cukup jelas bahwa jika saya dapat dengan jelas membagi kelas awal Anda menjadi dua yang masih memiliki antarmuka yang baik, kelas awal mungkin melakukan terlalu banyak, dan memang begitu.
Apa yang hebat tentang pola repositori, itu tidak hanya bertindak sebagai abstraksi untuk menyediakan lapisan tengah antara database (yang bisa berupa apa saja, file, noSQL, SQL, berorientasi objek), tetapi bahkan tidak perlu menjadi beton kelas. Dalam banyak bahasa OO, Anda dapat mendefinisikan antarmuka sebagai aktual
interface
(atau kelas dengan metode virtual murni jika Anda menggunakan C ++) dan kemudian memiliki beberapa implementasi.Ini sepenuhnya mengangkat keputusan apakah repositori adalah implementasi aktual Anda hanya mengandalkan antarmuka dengan benar-benar mengandalkan struktur dengan
interface
kata kunci. Dan repositori persis seperti itu, itu adalah istilah mewah untuk abstraksi lapisan data, yaitu memetakan data ke domain Anda dan sebaliknya.Hal hebat lainnya tentang memisahkannya ke dalam (setidaknya) dua kelas adalah bahwa sekarang
Employee
kelas dapat dengan jelas mengelola datanya sendiri dan melakukannya dengan sangat baik, karena ia tidak perlu mengurus hal-hal sulit lainnya.Pertanyaan 6: Jadi apa yang harus dilakukan oleh konstruktor di kelas yang baru dibuat
Employee
? Sederhana saja. Seharusnya mengambil argumen, memeriksa apakah mereka valid (seperti usia seharusnya tidak boleh negatif atau nama tidak boleh kosong), menimbulkan kesalahan ketika data tidak valid dan jika validasi yang dilewatkan menetapkan argumen ke variabel pribadi entitas. Sekarang tidak dapat berkomunikasi dengan database, karena ia tidak tahu bagaimana melakukannya.Pertanyaan 4: Tidak dapat dijawab sama sekali, tidak secara umum, karena jawabannya sangat tergantung pada apa yang sebenarnya Anda butuhkan.
Pertanyaan 5: Sekarang bahwa Anda telah memisahkan kelas membengkak menjadi dua, Anda dapat memiliki beberapa metode pembaruan langsung pada
Employee
kelas, sepertichangeUsername
,markAsDeceased
, yang akan memanipulasi data dariEmployee
kelas hanya dalam RAM dan kemudian Anda bisa memperkenalkan metode sepertiregisterDirty
dari Pola Unit Kerja ke kelas repositori, di mana Anda akan membiarkan repositori tahu bahwa objek ini telah mengubah properti dan perlu diperbarui setelah Anda memanggilcommit
metode.Jelas, untuk pembaruan suatu objek harus memiliki id dan karenanya sudah disimpan, dan itu adalah tanggung jawab repositori untuk mendeteksi ini dan meningkatkan kesalahan ketika kriteria tidak terpenuhi.
Pertanyaan 3: Jika Anda memutuskan untuk mengikuti pola Unit Kerja,
create
metode sekarang akan menjadiregisterNew
. Jika tidak, saya mungkin akan menyebutnya sebagaisave
gantinya. Tujuan dari repositori adalah untuk memberikan abstraksi antara domain dan lapisan data, karena ini saya akan merekomendasikan Anda bahwa metode ini (baik ituregisterNew
atausave
) menerimaEmployee
obyek dan terserah kepada kelas menerapkan antarmuka repositori, yang atribut mereka memutuskan untuk mengeluarkan entitas tersebut. Melewati seluruh objek lebih baik sehingga Anda tidak perlu memiliki banyak parameter opsional.Pertanyaan 2: Kedua metode sekarang akan menjadi bagian dari antarmuka repositori dan mereka tidak melanggar prinsip tanggung jawab tunggal. Tanggung jawab repositori adalah untuk menyediakan operasi CRUD untuk
Employee
objek, itulah yang dilakukannya (selain Baca dan Hapus, CRUD diterjemahkan menjadi Buat dan Perbarui). Jelas, Anda dapat membagi repositori lebih jauh dengan memilikiEmployeeUpdateRepository
dan sebagainya, tetapi itu jarang diperlukan dan implementasi tunggal biasanya dapat berisi semua operasi CRUD.Pertanyaan 1: Anda berakhir dengan
Employee
kelas sederhana yang sekarang (di antara atribut lainnya) memiliki id. Apakah id diisi atau kosong (ataunull
) tergantung pada apakah objek telah disimpan. Meskipun demikian, id masih merupakan atribut yang dimiliki entitas dan tanggung jawabEmployee
entitas adalah untuk menjaga atributnya, maka dari itu menjaga idnya.Entah suatu entitas memiliki atau tidak memiliki id, biasanya tidak penting sebelum Anda mencoba melakukan beberapa logika kegigihan padanya. Seperti disebutkan dalam jawaban untuk pertanyaan 5, itu adalah tanggung jawab repositori untuk mendeteksi Anda tidak mencoba menyelamatkan entitas yang sudah disimpan atau mencoba memperbarui entitas tanpa id.
Catatan penting
Perlu diketahui bahwa meskipun pemisahan kekhawatiran itu hebat, sebenarnya merancang lapisan repositori fungsional merupakan pekerjaan yang sangat membosankan dan menurut pengalaman saya sedikit lebih sulit untuk dilakukan dengan benar daripada pendekatan rekaman aktif. Tetapi Anda akan berakhir dengan desain yang jauh lebih fleksibel dan terukur, yang mungkin merupakan hal yang baik.
sumber
Pertama buat struktur karyawan yang berisi properti karyawan konseptual.
Kemudian buat database dengan struktur tabel yang cocok, katakan misalnya mssql
Kemudian buat repositori karyawan Untuk basis data itu, EmployeeRepoMsSql dengan berbagai operasi CRUD yang Anda butuhkan.
Kemudian buat antarmuka IEmployeeRepo yang mengekspos operasi CRUD
Kemudian rentangkan struct Karyawan Anda ke kelas dengan parameter konstruksi IEmployeeRepo. Tambahkan berbagai metode Simpan / Hapus dll yang Anda perlukan dan gunakan EmployeeRepo yang disuntikkan untuk menerapkannya.
Ketika cone ke Id saya sarankan Anda menggunakan GUID yang dapat dihasilkan melalui kode di konstruktor.
Untuk bekerja dengan objek yang ada, kode Anda dapat mengambilnya dari database melalui repositori sebelum memanggil Metode Pembaruan.
Atau Anda dapat memilih model objek Anemic Domain yang disukai (tetapi dalam pandangan saya superior) di mana Anda tidak menambahkan metode CRUD ke objek Anda, dan hanya meneruskan objek ke repo untuk diperbarui / disimpan / dihapus
Kekekalan adalah pilihan desain yang akan tergantung pada pola dan gaya penulisan Anda. Jika Anda semua fungsional maka cobalah untuk menjadi abadi juga. Tetapi jika Anda tidak yakin objek yang bisa berubah mungkin lebih mudah diimplementasikan.
Alih-alih Buat () saya akan pergi dengan Simpan (). Buat karya dengan konsep immutability, tetapi saya selalu merasa berguna untuk dapat membangun objek yang belum 'Disimpan' misalnya Anda memiliki beberapa UI yang memungkinkan Anda untuk mengisi objek atau objek karyawan dan kemudian memverifikasi lagi beberapa aturan sebelum menyimpan ke database.
***** contoh kode
sumber
UserUpdate
layanan denganchangeUsername(User user, string newUsername)
metode, padahal saya bisa juga menambahkanchangeUsername
metode ke kelasUser
secara langsung. Menciptakan layanan untuk itu tidak masuk akal.Tinjau desain Anda
Employee
Pada kenyataannya, Anda adalah semacam proksi untuk objek yang dikelola terus-menerus dalam database.Karena itu saya menyarankan untuk berpikir ke ID seolah-olah itu referensi ke objek database Anda. Dengan mengingat logika ini, Anda dapat melanjutkan desain seperti yang akan Anda lakukan untuk objek non basis data, ID yang memungkinkan Anda menerapkan logika komposisi tradisional:
Employee
mungkin belum dibuat, atau hanya bisa dihapus.Anda juga perlu mengelola status objek. Sebagai contoh:
Dengan mengingat hal ini, kita dapat memilih:
Agar dapat mengelola status objek Anda dengan cara yang dapat diandalkan, Anda harus memastikan enkapsulasi yang lebih baik dengan menjadikan properti pribadi dan memberikan akses hanya melalui getter dan setter yang mengatur status pembaruan.
Pertanyaan Anda
Saya pikir properti ID tidak melanggar SRP. Tanggung jawab tunggal adalah merujuk ke objek database.
Karyawan Anda secara keseluruhan tidak patuh dengan SRP, karena bertanggung jawab atas tautan dengan database, tetapi juga untuk menahan perubahan sementara, dan untuk semua transaksi yang terjadi pada objek tersebut.
Desain lain bisa untuk menjaga bidang yang dapat diubah di objek lain yang akan dimuat hanya ketika bidang perlu diakses.
Anda bisa menerapkan transaksi basis data pada Karyawan menggunakan pola perintah . Desain semacam ini juga akan memudahkan pemisahan antara objek bisnis Anda (Karyawan) dan sistem basis data Anda, dengan mengisolasi idiom dan API khusus basis data.
Saya tidak akan menambahkan selusin parameter
Create()
, karena objek bisnis dapat berkembang dan membuat semua ini sangat sulit untuk dipertahankan. Dan kode akan menjadi tidak terbaca. Anda memiliki 2 pilihan di sini: melewati serangkaian parameter minimalis (tidak lebih dari 4) yang benar-benar diperlukan untuk membuat karyawan di basis data dan melakukan perubahan yang tersisa melalui pembaruan, ATAU Anda melewatkan objek. By the way, dalam desain Anda Saya memahami bahwa Anda sudah dipilih:my_employee.Create()
.Haruskah kelas tidak berubah? Lihat diskusi di atas: dalam desain asli Anda no. Saya akan memilih ID yang tidak dapat diubah tetapi bukan Karyawan yang tidak dapat diubah. Seorang Karyawan berkembang dalam kehidupan nyata (posisi pekerjaan baru, alamat baru, situasi perkawinan baru, bahkan nama baru ...). Saya pikir akan lebih mudah dan lebih alami untuk bekerja dengan kenyataan ini dalam pikiran, setidaknya di lapisan logika bisnis.
Jika Anda mempertimbangkan untuk menggunakan perintah untuk pembaruan dan objek berbeda untuk (GUI?) Untuk menahan perubahan yang diinginkan, Anda dapat memilih pendekatan lama / baru. Dalam semua kasus lain, saya akan memilih untuk memperbarui objek yang bisa berubah. Perhatian: pembaruan dapat memicu kode database sehingga Anda harus memastikan setelah pembaruan objek masih benar-benar selaras dengan DB.
Saya pikir bahwa mengambil karyawan dari DB di konstruktor bukanlah ide yang baik, karena mengambil bisa salah, dan dalam banyak bahasa, sulit untuk mengatasi konstruksi yang gagal. Konstruktor harus menginisialisasi objek (terutama ID) dan statusnya.
sumber