Menyimpan objek melalui metode sendiri atau melalui kelas lain?

38

Jika saya ingin menyimpan dan mengambil objek, haruskah saya membuat kelas lain untuk menanganinya, atau lebih baik melakukannya di kelas itu sendiri? Atau mungkin mencampur keduanya?

Mana yang direkomendasikan sesuai dengan paradigma OOD?

Sebagai contoh

Class Student
{
    public string Name {set; get;}
    ....
    public bool Save()
    {
        SqlConnection con = ...
        // Save the class in the db
    }
    public bool Retrieve()
    {
         // search the db for the student and fill the attributes
    }
    public List<Student> RetrieveAllStudents()
    {
         // this is such a method I have most problem with it 
         // that an object returns an array of objects of its own class!
    }
}

Melawan. (Saya tahu yang berikut ini direkomendasikan, namun menurut saya agak bertentangan dengan kohesi Studentkelas)

Class Student { /* */ }
Class DB {
  public bool AddStudent(Student s)
  {

  }
  public Student RetrieveStudent(Criteria)
  {
  } 
  public List<Student> RetrieveAllStudents()
  {
  }
}

Bagaimana kalau mencampurkannya?

   Class Student
    {
        public string Name {set; get;}
        ....
        public bool Save()
        {
            /// do some business logic!
            db.AddStudent(this);
        }
        public bool Retrieve()
        {
             // build the criteria 
             db.RetrieveStudent(criteria);
             // fill the attributes
        }
    }
Ahmad
sumber
3
Anda harus melakukan penelitian pada pola Rekaman Aktif, seperti pertanyaan ini
Eric King
@gnat, saya pikir bertanya mana yang lebih baik berdasarkan pendapat kemudian menambahkan "pro dan kontra" dan tidak memiliki masalah untuk menghapusnya, tetapi sebenarnya saya maksudkan mengenai paradigma OOD yang direkomendasikan
Ahmad
3
Tidak ada jawaban yang benar. Itu tergantung pada kebutuhan Anda, preferensi Anda, bagaimana kelas Anda akan digunakan dan di mana Anda melihat proyek Anda berjalan. Satu-satunya OO yang benar adalah Anda dapat menyimpan dan mengambil sebagai objek.
Dunk

Jawaban:

34

Prinsip Tanggung Jawab Tunggal , Pemisahan Kekhawatiran dan Kohesi Fungsional . Jika Anda membaca konsep-konsep ini, jawaban yang Anda dapatkan adalah: Pisahkan mereka .

Alasan sederhana untuk memisahkan Student dari kelas "DB" (atau StudentRepository, untuk mengikuti konvensi yang lebih populer) adalah untuk memungkinkan Anda mengubah "aturan bisnis" Anda, yang ada di Studentkelas, tanpa memengaruhi kode yang bertanggung jawab atas ketekunan, dan sebaliknya. sebaliknya.

Pemisahan seperti ini sangat penting, tidak hanya antara aturan bisnis dan kegigihan, tetapi di antara banyak masalah sistem Anda, untuk memungkinkan Anda melakukan perubahan dengan dampak minimal pada modul yang tidak terkait (minimal karena kadang-kadang tidak dapat dihindari). Ini membantu untuk membangun sistem yang lebih kuat, yang lebih mudah untuk dipelihara dan lebih dapat diandalkan ketika ada perubahan konstan.

Dengan menggabungkan aturan bisnis dan kegigihan, baik satu kelas seperti pada contoh pertama Anda, atau dengan DBketergantungan Student, Anda menggabungkan dua masalah yang sangat berbeda. Itu mungkin terlihat seperti milik mereka bersama; mereka tampaknya kohesif karena mereka menggunakan data yang sama. Tetapi ada satu hal: kohesi tidak dapat diukur hanya dengan data yang dibagikan di antara prosedur, Anda juga harus mempertimbangkan tingkat abstraksi di mana mereka ada. Faktanya, tipe kohesi yang ideal digambarkan sebagai:

Kohesi fungsional adalah ketika bagian-bagian modul dikelompokkan karena semuanya berkontribusi pada satu tugas modul yang didefinisikan dengan baik.

Dan jelas, melakukan validasi tentang Studentsementara juga bertahan itu tidak membentuk "tugas tunggal yang jelas". Jadi sekali lagi, aturan bisnis dan mekanisme kegigihan adalah dua aspek yang sangat berbeda dari sistem, yang oleh banyak prinsip desain berorientasi objek yang baik, harus disimpan terpisah.

Saya sarankan membaca tentang Arsitektur Bersih , menonton pembicaraan ini tentang Prinsip Tanggung Jawab Tunggal (di mana contoh yang sangat mirip digunakan), dan menonton pembicaraan ini tentang Arsitektur Bersih . Konsep-konsep ini menguraikan alasan di balik pemisahan tersebut.

MichelHenrich
sumber
11
Ah, tapi Studentkelasnya harus dienkapsulasi dengan benar, ya? Bagaimana kemudian kelas eksternal dapat mengelola kegigihan negara swasta? Enkapsulasi harus seimbang terhadap SoC dan SRP, tetapi hanya memilih salah satu dari yang lain tanpa dengan hati-hati menimbang timbal balik mungkin salah. Solusi yang memungkinkan untuk teka-teki ini adalah menggunakan pengakses paket-pribadi untuk menggunakan kode kegigihan.
amon
1
@amon Saya setuju bahwa ini tidak sesederhana itu, tapi saya percaya ini adalah masalah penggandengan, bukan enkapsulasi. Misalnya, Anda dapat membuat abstrak kelas Siswa, dengan metode abstrak untuk setiap data yang dibutuhkan bisnis, yang kemudian diimplementasikan oleh repositori. Dengan cara ini, struktur data DB sepenuhnya tersembunyi di balik implementasi repositori, sehingga bahkan dienkapsulasi dari kelas Siswa. Tetapi, pada saat yang sama, repositori sangat digabungkan ke kelas Student - jika ada metode abstrak yang diubah, implementasinya juga harus berubah. Ini semua tentang pertukaran. :)
MichelHenrich
4
Klaim bahwa SRP membuat program lebih mudah dipelihara adalah meragukan. Masalah yang SRP ciptakan adalah bahwa alih-alih memiliki koleksi kelas yang diberi nama baik, di mana namanya memberi tahu segalanya yang dapat dan tidak dapat dilakukan oleh kelas (dengan kata lain mudah dipahami yang mengarah ke pemeliharaan yang mudah) Anda berakhir dengan ratusan / ribuan kelas yang orang perlu menggunakan tesaurus untuk memilih nama yang unik, banyak nama yang mirip tetapi tidak cukup dan memilah-milah semua bahwa sekam adalah tugas. TKI, tidak bisa dirawat sama sekali, IMO.
Dunk
3
@Dunk Anda berbicara seolah-olah kelas adalah satu-satunya unit modularisasi yang tersedia. Saya telah melihat dan menulis banyak proyek mengikuti SRP, dan saya setuju bahwa contoh Anda memang terjadi, tetapi hanya jika Anda tidak menggunakan paket dan pustaka dengan benar. Jika Anda memisahkan tanggung jawab ke dalam paket, karena konteksnya tersirat olehnya, Anda dapat memberi nama kelas-kelas itu lagi dengan bebas. Kemudian, untuk mempertahankan sistem seperti ini, yang diperlukan hanyalah melakukan drill-drown ke paket yang tepat sebelum mencari kelas yang perlu Anda ubah. :)
MichelHenrich
5
Prinsip @Dunk OOD dapat dinyatakan sebagai: "Pada prinsipnya, melakukan ini lebih baik karena X, Y dan Z". Itu tidak berarti bahwa itu harus selalu diterapkan; orang harus pragmatis dan mempertimbangkan timbal baliknya. Dalam proyek-proyek besar, manfaatnya sedemikian rupa sehingga biasanya lebih besar daripada kurva belajar yang Anda bicarakan - tetapi tentu saja, itu bukan kenyataan bagi kebanyakan orang, dan karenanya tidak boleh diterapkan secara berlebihan. Namun, bahkan dalam aplikasi SRP yang lebih ringan, saya pikir kita berdua bisa sepakat bahwa memisahkan kegigihan dari aturan bisnis selalu menghasilkan keuntungan di luar biayanya (kecuali mungkin untuk kode yang dibuang).
MichelHenrich
10

Kedua pendekatan tersebut melanggar Prinsip Tanggung Jawab Tunggal. Versi pertama Anda memberi Studentkelas pada banyak tanggung jawab dan mengikatnya dengan teknologi akses DB tertentu. Yang kedua mengarah ke DBkelas besar yang akan bertanggung jawab tidak hanya untuk siswa, tetapi untuk segala jenis objek data dalam program Anda. EDIT: pendekatan ketiga Anda adalah yang terburuk, karena itu menciptakan ketergantungan siklik antara kelas DB danStudent kelas.

Jadi, jika Anda tidak akan menulis program mainan, jangan gunakan satu pun. Alih-alih, gunakan kelas yang berbeda seperti StudentRepositoryuntuk menyediakan API untuk memuat dan menyimpan, dengan asumsi Anda akan mengimplementasikan kode CRUD sendiri. Anda mungkin juga mempertimbangkan untuk menggunakan kerangka kerja ORM , yang dapat melakukan kerja keras untuk Anda (dan kerangka kerja tersebut biasanya akan menegakkan beberapa keputusan di mana operasi Muat dan Simpan harus ditempatkan).

Doc Brown
sumber
Terima kasih, saya memodifikasi jawaban saya, bagaimana dengan pendekatan ketiga? Lagi pula saya pikir di sini intinya adalah tentang membuat lapisan yang berbeda
Ahmad
Sesuatu juga menyarankan saya untuk menggunakan StudentRepository, bukan DB (tidak tahu mengapa! Mungkin lagi tanggung jawab tunggal) tetapi saya masih tidak bisa mendapatkan mengapa repositori berbeda untuk setiap kelas? jika itu hanya lapisan akses data maka ada lebih banyak fungsi utilitas statis dan dapat bersama-sama, bukan? mungkin saja itu akan menjadi kelas yang sangat kotor dan ramai (maaf untuk bahasa Inggris saya)
Ahmad
1
@Ahmad: ada beberapa alasan, dan beberapa pro dan kontra untuk dipertimbangkan. Ini bisa mengisi seluruh buku. Alasan di balik ini bermuara pada testabilitas, rawatan dan evolusi jangka panjang dari program Anda, terutama ketika mereka memiliki siklus hidup yang tahan lama.
Doc Brown
Adakah yang pernah mengerjakan proyek di mana ada perubahan signifikan dalam teknologi DB? (tanpa penulisan ulang besar-besaran?) Saya belum. Jika demikian, apakah mengikuti SRP, seperti yang disarankan di sini untuk menghindari terikat pada DB, membantu secara signifikan?
user949300
@ user949300: Saya rasa saya tidak mengekspresikan diri saya dengan jelas. Kelas siswa tidak menjadi terikat pada teknologi DB tertentu, ia menjadi terikat pada teknologi akses DB tertentu, sehingga ia mengharapkan sesuatu seperti SQL DB untuk kegigihan. Menjaga kelas Siswa benar-benar tidak menyadari mekanisme kegigihan memang memungkinkan penggunaan kembali kelas dengan lebih mudah dalam konteks yang berbeda. Misalnya, dalam konteks pengujian, atau dalam konteks di mana objek disimpan dalam file. Dan itu adalah sesuatu yang sangat sering saya lakukan di masa lalu. Tidak perlu mengubah seluruh tumpukan DB sistem Anda untuk mendapatkan manfaat dari ini.
Doc Brown
3

Ada beberapa pola yang dapat digunakan untuk kegigihan data. Ada pola Unit Kerja , ada pola Repositori , ada beberapa pola tambahan yang bisa digunakan seperti Remote Facade, dan seterusnya.

Sebagian besar dari mereka memiliki penggemar dan kritik mereka. Seringkali datang untuk memilih apa yang tampaknya paling sesuai dengan aplikasi dan tetap menggunakannya (dengan semua kelebihan dan kekurangannya, yaitu tidak menggunakan kedua pola pada saat yang sama ... kecuali Anda benar-benar yakin tentang itu).

Sebagai catatan tambahan: dalam contoh Anda RetrieveStudent, AddStudent harus berupa metode statis (karena mereka tidak bergantung pada instance).

Cara lain untuk memiliki metode simpan / muat di kelas adalah:

class Animal{
    public string Name {get; set;}
    public void Save(){...}
    public static Animal Load(string name){....}
    public static string[] GetAllNames(){...} // if you just want to list them
    public static Animal[] GetAllAnimals(){...} // if you actually need to retrieve them all
}

Secara pribadi saya akan menggunakan pendekatan seperti itu hanya dalam aplikasi yang cukup kecil, mungkin alat untuk penggunaan pribadi atau di mana saya dapat memprediksi bahwa saya tidak akan memiliki kasus penggunaan yang lebih rumit daripada hanya menyimpan atau memuat objek.

Juga secara pribadi, lihat pola Unit Kerja. Ketika Anda mengetahuinya, itu sangat bagus dalam kasus kecil dan besar. Dan itu didukung oleh banyak framework / apis, untuk memberi nama EntityFramework atau RavenDB misalnya.

Gerino
sumber
2
Tentang catatan samping Anda: Meskipun, secara konseptual, hanya ada satu DB saat aplikasi sedang berjalan, itu tidak berarti Anda harus membuat RetriveStudent dan metode AddStudent statis. Repositori biasanya dibuat dengan antarmuka untuk memungkinkan pengembang untuk beralih implementasi tanpa mempengaruhi sisa aplikasi. Ini bahkan dapat memungkinkan Anda untuk mendistribusikan aplikasi Anda dengan dukungan untuk database yang berbeda, misalnya. Logika yang sama ini, tentu saja, berlaku untuk banyak bidang lain selain ketekunan, seperti, misalnya, UI.
MichelHenrich
Anda pasti benar, saya telah membuat banyak asumsi berdasarkan pertanyaan awal.
Gerino
terima kasih, saya pikir pendekatan terbaik adalah menggunakan lapisan seperti yang disebutkan dalam pola Repositori
Ahmad
1

Jika itu adalah aplikasi yang sangat sederhana di mana objek kurang lebih terikat pada datastore dan sebaliknya (yaitu dapat dianggap sebagai properti dari datastore), maka memiliki metode .save () untuk kelas itu mungkin masuk akal.

Tapi saya pikir itu akan sangat luar biasa.

Sebaliknya, biasanya lebih baik membiarkan kelas mengelola data dan fungsinya (seperti warga negara OO yang baik), dan mengeksternalisasi mekanisme ketekunan ke kelas lain, atau sekumpulan kelas.

Pilihan lain adalah dengan menggunakan kerangka kegigihan yang mendefinisikan kegigihan secara deklaratif (seperti dengan anotasi), tetapi itu masih mengeksternalisasi kegigihan.

rampok
sumber
-1
  • Tentukan metode pada kelas yang membuat serial objek, dan mengembalikan urutan byte yang dapat Anda masukkan ke dalam database atau file atau apa pun
  • Memiliki konstruktor untuk objek yang mengambil urutan byte sebagai input

Tentang RetrieveAllStudents()metode ini, perasaan Anda benar, mungkin memang salah tempat, karena Anda mungkin memiliki beberapa daftar siswa yang berbeda. Mengapa tidak menyimpan saja daftar di luar Studentkelas?

philfr
sumber
Anda tahu saya telah melihat tren seperti itu dalam beberapa kerangka kerja PHP misalnya Laravel jika Anda mengetahuinya. Model ini terikat ke database dan bahkan dapat mengembalikan serangkaian objek.
Ahmad