Bir nesneyi kendine ait bir yöntemle veya başka bir sınıfla mı kaydetmek?


38

Bir nesneyi kaydetmek ve almak istersem, onu işlemek için başka bir sınıf mı oluşturmalıyım, yoksa bunu sınıfın kendisinde mi yapmalıyım? Ya da belki her ikisini de karıştırıyor?

OOD paradigmasına göre hangisi önerilir?

Örneğin

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!
    }
}

E karşı. (Aşağıdakilerin tavsiye edildiğini biliyorum, ancak bana Studentsınıfın uyumuna karşı biraz görünüyor )

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

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

Onları karıştırmaya ne dersiniz?

   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
        }
    }


@gnat, hangisinin daha iyi olduğunu sormak fikrine dayalı olduğunu düşündükten sonra "artıları ve eksileri" ekledi ve kaldırmak için hiçbir problemi olmadı, ancak aslında önerilen OOD paradigması ile ilgili demek istiyorum
Ahmad,

3
Doğru bir cevap yok. İhtiyaçlarınıza, tercihlerinize, sınıfınızın nasıl kullanılacağına ve projenizin nereye gittiğini gördüğünüze bağlıdır. Tek doğru OO olayı, nesneler olarak kaydedip alabilmenizdir.
Dunk

Yanıtlar:


34

Tek Sorumluluk İlkesi , Endişelerin Ayrılması ve İşlevsel Uyum . Bu kavramları okuyorsanız, aldığınız cevap: Onları ayırın .

Student"DB" sınıfından (veya StudentRepositorydaha popüler sözleşmelere uymak için) ayrılmanın basit bir nedeni , Studentsınıfta mevcut olan "iş kurallarınızı" kalıcılıktan sorumlu kodu etkilemeden değiştirmenize izin vermektir. versa.

Bu tür ayrılıklar, sadece iş kuralları ve ısrarlar arasında değil, aynı zamanda ilgisiz modüller üzerinde minimum etkiye sahip değişiklikler yapmanıza izin vermek için sisteminizin birçok kaygısı arasında da çok önemlidir (en azından çünkü kaçınılmazdır). Sürekli değişiklikler olduğunda bakımı daha kolay olan ve daha güvenilir olan daha sağlam sistemler kurmaya yardımcı olur.

İş kurallarını ve kalıcılığı bir araya getirerek, ya ilk örneğinizdeki gibi tek bir sınıf ya DBda bir bağımlılık olarak Student, çok farklı iki kaygıyı birleştiriyorsunuz. Birbirlerine aitmiş gibi görünebilir; aynı verileri kullandıkları için uyumlu görünüyorlar. Fakat işte mesele: uyum yalnızca prosedürler arasında paylaşılan verilerle ölçülemez, aynı zamanda var oldukları soyutlama seviyesini de göz önünde bulundurmalısınız. Aslında, ideal uyum tipi:

İşlevsel uyum , bir modülün parçaları gruplandığı zamandır, çünkü hepsi modülün iyi tanımlanmış tek bir görevine katkıda bulunur.

Ve açıkça, bir Studentsüredir devam eden doğrulamaların yapılması, "iyi tanımlanmış tek bir görev" oluşturmaz. Bu yüzden yine, iş kuralları ve sebat mekanizmaları, sistemin iyi nesneye yönelik tasarım prensipleriyle ayrı tutulması gereken çok farklı iki yönüdür.

Temiz Mimari hakkında okumayı , Tek Sorumluluk İlkesi (çok benzer bir örnek kullanıldığında) hakkındaki konuşmaları izlemenizi ve Temiz Mimari hakkında da bu konuşmayı izlemenizi öneririm . Bu kavramlar, bu ayrımların ardındaki nedenleri özetlemektedir.


11
Ah, ama Studentsınıf uygun şekilde kapsüllenmiş olmalı, evet? O zaman, bir dış sınıf özel devletin kalıcılığını nasıl yönetebilir? Kapsülleme, SoC ve SRP'ye karşı dengelenmiş olmalıdır, ancak sadece tradeoffları dikkatlice tartılamadan ötekinden birini seçmek muhtemelen yanlıştır. Bu bilmeceye olası bir çözüm, kullanımda kalıcılık kodu için özel paket erişimcileri kullanmaktır.
amon

1
@ amon Çok basit olmadığına katılıyorum, fakat bunun bir kaplin meselesi olduğuna inanıyorum. Örneğin, Öğrenci sınıfını, işletmenin ihtiyaç duyduğu tüm veriler için, daha sonra depo tarafından uygulanan soyut yöntemlerle soyut yapabilirsiniz. Bu şekilde, DB'nin veri yapısı depo uygulamasının arkasına tamamen gizlenir, böylece Öğrenci sınıfından bile kapsüllenir. Ancak, aynı zamanda, depo Öğrenci sınıfına derinden bağlıdır - eğer herhangi bir soyut yöntem değişirse, uygulamanın da değişmesi gerekir. Her şey değiş tokuş ile ilgili. :)
MichelHenrich

4
SRP'nin programları sürdürmeyi kolaylaştırdığı iddiası en iyi ihtimalle şüphelidir. SRP'nin yarattığı sorun, adın sınıfın yapabileceği ve yapamayacağı herşeyi söylediği (yani, anlaşılması kolay olanı söylemek kolaydır) iyi adlandırılmış sınıfların bir koleksiyonuna sahip olmak yerine yüzlerce / Eşsiz bir isim seçmek için insanların eş anlamlılar sözlüğü kullanması gereken binlerce sınıf, pek çok isim benzer ancak bunlardan tamamen değil ve bu ahırı sıralamak bir angaryadır. IOW, Hiç de bakımı mümkün değil, IMO.
Dunk

3
@ Sence, sınıflar mevcut tek modülerleştirme ünitesiymiş gibi konuşuyorsunuz. SRP'yi takiben birçok proje gördüm ve yazdım ve örneğinizin gerçekleşeceğine katılıyorum, ancak yalnızca paketleri ve kitaplıkları doğru şekilde kullanmazsanız. Sorumlulukları paketlere ayırırsanız, bağlamın öngördüğü için, sınıfları serbestçe yeniden adlandırabilirsiniz. Daha sonra, böyle bir sistemi sürdürmek için tek yapmanız gereken değiştirmeniz gereken sınıfı aramadan önce doğru pakete boğulmaktır. :)
MichelHenrich

5
@Dunk OOD prensipleri şu şekilde ifade edilebilir: "Prensip olarak, bunu yapmak X, Y ve Z nedeniyle daha iyidir". Her zaman uygulanması gerektiği anlamına gelmez; insanlar pragmatik olmalı ve değiş tokuşları yapmalılar. Büyük projelerde, faydaları, bahsettiğiniz öğrenme eğrisinden daha ağır basacak şekildedir - ama elbette, çoğu insan için gerçek değildir ve bu yüzden çok yoğun uygulanmamalıdır. Ancak, daha hafif olan SRP uygulamalarında bile, her ikisinin de sebatın iş kurallarından ayrılmasının maliyetinin ötesinde faydalar sağlayabileceği konusunda hemfikir olduğumuzu düşünüyorum (belki de çöpe atma kodu hariç).
MichelHenrich

10

Her iki yaklaşım da Tek Sorumluluk İlkesini ihlal ediyor. İlk versiyon, Studentsınıfa birçok sorumluluk verir ve belirli bir DB erişim teknolojisine bağlar. İkincisi, DByalnızca öğrencilerden değil programınızdaki diğer herhangi bir veri nesnesinden sorumlu olacak büyük bir sınıfa yol açar . EDIT: üçüncü yaklaşımınız en kötüsüdür, çünkü DB sınıfı ve sınıf arasında döngüsel bir bağımlılık yaratır Student.

Eğer bir oyuncak programı yazmayacaksanız, hiçbirini kullanmayın. Bunun yerine, StudentRepositoryCRUD kodunu kendi başınıza uygulayacağınızı farz edersek, yükleme ve kaydetme için bir API sağlamak için farklı bir sınıf kullanın . Ayrıca , sizin için zor olan işleri yapabilen bir ORM çerçevesi kullanmayı düşünebilirsiniz (ve çerçeve genellikle Yükleme ve Kaydet işlemlerinin yerleştirilmesi gereken bazı kararları uygular).


Teşekkürler, cevabımı değiştirdim, peki ya üçüncü yaklaşım? Neyse, burada
Ahmad,

Bir şey de bana DB yerine StudentRepository kullanmamı önerdi (nedenini bilmiyorum! Belki yine tek sorumluluk) ama yine de her sınıf için neden farklı depo alamıyorum? onun sadece bir veri erişim katmanı olması durumunda, daha fazla statik fayda fonksiyonu vardır ve birlikte olabilirler, değil mi? sadece belki o zaman çok kirli ve kalabalık bir sınıf haline gelirdi (İngilizcem için özür dilerim)
Ahmad

1
@Ahmad: Bir kaç neden var, ve düşünülecek birkaç artı ve eksiler var. Bu, bütün bir kitabı doldurabilir. Bunun arkasındaki neden, özellikle uzun ömürlü bir yaşam döngüsüne sahip olduklarında, programlarınızın test edilebilirliği, bakımı ve uzun vadeli evrimleşmesine neden olur.
Doktor Brown,

DB teknolojisinde önemli bir değişikliğin olduğu bir proje üzerinde çalışan var mı? (büyük bir yeniden yazma olmadan?) Yapmadım. Öyleyse, burada DB'ye bağlı kalmaktan kaçınmak için önerildiği gibi SRP'yi takip etmek önemli ölçüde yardımcı oldu mu?
kullanıcı949300

@ user949300: Kendimi açıkça ifade etmediğimi düşünüyorum. Öğrenci sınıfı belirli bir DB teknolojisine bağlı olmaz, belirli bir DB erişim teknolojisine bağlanır, bu nedenle kalıcılık için SQL DB gibi bir şey bekler. Öğrenci sınıfını kalıcılık mekanizmasından tamamen habersiz tutmak, sınıfın farklı bağlamlarda tekrar kullanılmasını kolaylaştırır. Örneğin, bir test bağlamında veya nesnelerin bir dosyada saklandığı bir bağlamda. Ve bu geçmişte gerçekten çok sık yaptığım bir şey. Bundan yararlanmak için sisteminizin tüm DB yığınını değiştirmenize gerek yok.
Doktor Brown,

3

Veri kalıcılığı için kullanılabilecek birkaç kalıp vardır. Orada çalışmak Birim deseni vardır, Depo ve bu kadar uzaktan Cephe gibi kullanılabilir ve bazı ek desenler vardır, deseni.

Bunların çoğunun hayranları ve eleştirmenleri var. Genelde, uygulamaya en uygun olanı seçmek ve buna bağlı kalmak (geleceğin ve dezavantajları olan, yani aynı anda her iki modeli de kullanmamak ... aynı zamanda emin değilseniz) gelir.

Bir yan not olarak: Örneğinizde, RetrieveStudent, AddStudent, statik yöntemler olmalıdır (çünkü bunlar örnek bağımlı değildir).

Sınıf içi kaydetme / yükleme yöntemlerine sahip olmanın başka bir yolu:

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
}

Şahsen, bu tür bir yaklaşımı sadece oldukça küçük uygulamalarda, muhtemelen kişisel kullanım için olan araçlarda veya yalnızca nesneleri kaydetmek veya yüklemek yerine daha karmaşık kullanım durumlarına sahip olmayacağımı güvenilir bir şekilde tahmin edebileceğim durumlarda kullanırdım.

Ayrıca kişisel olarak, İş Birimi modeline bakın. Bunu tanıdığınızda, hem küçük hem de büyük durumlarda gerçekten iyidir. Ve örneğin EntityFramework veya RavenDB'yi isimlendirmek için birçok çerçeve / apis tarafından desteklenir.


2
Yan notunuz hakkında: Uygulama çalışırken kavramsal olarak yalnızca bir DB olmasına rağmen, bu RetriveStudent ve AddStudent yöntemlerini statik hale getirmeniz gerektiği anlamına gelmez. Depolar genellikle geliştiricilerin uygulamanın geri kalanını etkilemeden uygulamaları değiştirmesine izin vermek için arayüzlerle yapılır. Bu, örneğin, uygulamanızı farklı veritabanlarını destekleyerek dağıtabilmenizi sağlar. Elbette bu aynı mantık, örneğin kullanıcı arayüzü gibi sebat dışında diğer birçok alan için de geçerlidir.
MichelHenrich

Kesinlikle haklısın, orijinal soruyu temel alan birçok varsayım yaptım.
Gerino

teşekkür ederim, bence en iyi yaklaşım depo şablonunda belirtildiği gibi katmanları kullanmaktır
Ahmad

1

Nesnenin veri deposuna ya da tam tersine bağlı olduğu çok basit bir uygulama ise (yani veri deposunun bir özelliği olarak düşünülebilir), o zaman bu sınıf için bir .save () yöntemi kullanmak mantıklı olabilir.

Fakat bunun oldukça istisnai olacağını düşünüyorum.

Aksine, sınıfın verilerini ve işlevselliğini (iyi bir OO vatandaşı gibi) yönetmesini sağlamak ve kalıcılık mekanizmasını başka bir sınıfa veya sınıflara dışlamak için genellikle daha iyidir.

Diğer seçenek, ısrarı bildirimsel olarak tanımlayan bir kalıcılık çerçevesi kullanmaktır (ek açıklamalarda olduğu gibi), ancak bu da ısrarı dışsallaştırmaktadır.


-1
  • Nesneyi serileştiren bir sınıf tanımlayın ve bir veritabanına veya dosyaya koyabileceğiniz bir bayt sırası döndürür.
  • Girdi gibi bir bayt dizisi alan bu nesne için bir yapıcıya sahip olma

Hakkında RetrieveAllStudents()Eğer öğrenci birden çok farklı listeleri olabilir çünkü yöntemine bağlı olarak, duygu, sağ, gerçekten muhtemelen yanlış olmasıdır. Neden listeyi / Studentsınıfları sınıf dışında tutmuyorsun ?


Bildiğiniz gibi bazı PHP çerçevelerinde böyle bir eğilim gördüğümü biliyorsunuz, örneğin Laravel. Model veritabanına bağlı ve hatta bir dizi nesneyi geri döndürebilir.
Ahmad,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.