Neden bir sınıf miras alıyor ve özellik eklemiyorsunuz?


39

Böyle bir şeye giden (oldukça büyük) kod tabanımızda bir devralma ağacı buldum:

public class NamedEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class OrderDateInfo : NamedEntity { }

Toplayabildiğim kadarıyla, bu öncelikle ön uçtaki şeyleri bağlamak için kullanılır.

Benim için bu, jenere güvenmek yerine sınıfa somut bir isim verdiği için anlamlıdır NamedEntity. Öte yandan, ek bir özelliği olmayan birtakım sınıflar vardır.

Bu yaklaşımın herhangi bir dezavantajı var mı?


25
Upside bahsedilmedi: Sadece OrderDateInfos ile ilgili olan yöntemleri diğer NamedEntitys ile ilgili olanlardan
ayırabilirsiniz

8
Sahip bir şey olursa Aynı nedenle, diyelim ki, bir o Identifiersınıf olan hiçbir şey ile ilgisi NamedEntityancak her ikisi gerektiren Idve Nameözelliği, kullanmak olmaz NamedEntitybunun yerine. Bir sınıfın bağlamı ve uygun kullanımı, sahip olduğu özellik ve yöntemlerden daha fazlasıdır.
Neil

Yanıtlar:


15

Benim için bu, genel NamedEntity'ye güvenmek yerine sınıfa somut bir isim vermesi anlamlıdır. Öte yandan, ek bir özelliği olmayan birtakım sınıflar vardır.

Bu yaklaşımın herhangi bir dezavantajı var mı?

Yaklaşım fena değil, ancak daha iyi çözümler mevcut. Kısacası, bir arayüz bunun için çok daha iyi bir çözüm olacaktır. Arayüzlerin ve kalıtımın farklı olmasının temel nedeni , yalnızca bir sınıftan miras alabilmeniz, ancak birçok arayüzü uygulayabilmenizdir .

Örneğin, varlıklar ve denetlenmiş varlıklar olduğunuzu düşünün. Birkaç varlığınız var:

Onedenetlenmiş bir varlık veya adlandırılmış bir varlık değildir. Bu basit:

public class One 
{ }

Twoadlandırılmış bir varlıktır ancak denetlenmiş bir varlık değildir. Esasen şu an sahip olduğunuz şey bu:

public class NamedEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Two : NamedEntity 
{ }

Threehem adlandırılmış hem de denetlenmiş bir girdidir. Bir problemin olduğu yer burası. Bir AuditedEntitytemel sınıf oluşturabilirsiniz , ancak ikisini deThree devralamazsınız ve : AuditedEntity NamedEntity

public class AuditedEntity
{
    public DateTime CreatedOn { get; set; }
    public DateTime UpdatedOn { get; set; }
}

public class Three : NamedEntity, AuditedEntity  // <-- Compiler error!
{ }

Ancak, AuditedEntitymiras alarak bir geçici çözüm düşünebilirsiniz NamedEntity. Bu, her bir sınıfın yalnızca (doğrudan) başka bir sınıftan miras alması gerektiğinden emin olmak için akıllıca bir keskidir.

public class AuditedEntity : NamedEntity
{
    public DateTime CreatedOn { get; set; }
    public DateTime UpdatedOn { get; set; }
}

public class Three : AuditedEntity
{ }

Bu hala çalışıyor. Ancak burada yaptığınız, denetlenen her bir varlığın doğal olarak aynı zamanda bir adlandırılmış varlık olduğu belirtilmektedir . Bu beni son örneğime getiriyor. Fourdenetlenmiş bir varlıktır ancak adlandırılmış bir varlık değildir Ama izin veremem Fourgelen devralır AuditedEntitysonra da bir yapım olacağını olarak NamedEntityAuditedEntity arasındaki miras nedeniyle andNamedEntity`.

Kalıtım kullanarak, her ikisini de yapmanın Threeve Fourdersleri çoğaltmaya başlamadıkça (bu, tamamen yeni bir sorun kümesi açar) çalışmaz.

Arabirimleri kullanarak, bu kolayca elde edilebilir:

public interface INamedEntity
{
    int Id { get; set; }
    string Name { get; set; }
}

public interface IAuditedEntity
{
    DateTime CreatedOn { get; set; }
    DateTime UpdatedOn { get; set; }
}

public class One 
{ }

public class Two : INamedEntity 
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Three : INamedEntity, IAuditedEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    DateTime CreatedOn { get; set; }
    DateTime UpdatedOn { get; set; }
}

public class Four : IAuditedEntity
{
    DateTime CreatedOn { get; set; }
    DateTime UpdatedOn { get; set; }
}

Buradaki tek küçük dezavantajı, hala arayüzü uygulamak zorunda olmasıdır. Ancak, belirli bir varlık için birden fazla ortak türde değişiklik yapılması gerektiğinde ortaya çıkan dezavantajları olmadan, ortak bir yeniden kullanılabilir tipe sahip olmanın tüm faydalarını elde edersiniz.

Fakat polimorfizminiz bozulmadan kaldı:

var one = new One();
var two = new Two();
var three = new Three();
var four = new Four();

public void HandleNamedEntity(INamedEntity namedEntity) {}
public void HandleAuditedEntity(IAuditedEntity auditedEntity) {}

HandleNamedEntity(one);    //Error - not a named entity
HandleNamedEntity(two);
HandleNamedEntity(three);  
HandleNamedEntity(four);   //Error - not a named entity

HandleAuditedEntity(one);    //Error - not an audited entity
HandleAuditedEntity(two);    //Error - not an audited entity
HandleAuditedEntity(three);  
HandleAuditedEntity(four);

Öte yandan, ek bir özelliği olmayan birtakım sınıflar vardır.

Bu, sadece belirli bir sınıfın bu arayüzle "işaretlenmiş" olup olmadığını kontrol etmek için arayüz tipini kullanabilmek için sadece arayüz arayüzünü kullanabilmek için boş bir arayüz uyguladığınız marker arayüz modelinde bir varyasyondur .
Uygulanan arabirimler yerine kalıtsal sınıfları kullanıyorsunuz, ancak amaç aynı, bu yüzden "işaretli bir sınıf" olarak bahsedeceğim.

Yüz değerinde, işaretleyici arabirimleri / sınıflarında yanlış bir şey yoktur. Sözdizimsel ve teknik olarak geçerlidirler ve işaretleyicinin evrensel olarak doğru olması (derleme zamanında) ve koşullu olmaması şartıyla bunları kullanmanın sakıncaları yoktur .

Bu tam olarak, istisnalar arasında, temel istisna ile karşılaştırıldığında herhangi bir ek özellik / yöntem olmasa bile, farklı istisnalar arasında nasıl ayrım yapmanız gerektiğidir.

Yani bunu yaparken doğal olarak yanlış bir şey yok, ama sadece temkinli bir şekilde, kötü tasarlanmış polimorfizmle var olan bir mimari hatayı örtbas etmeye çalışmadığınızdan emin olmanızı öneririm.


4
"sadece bir sınıftan miras alabilirsin, ama birçok arayüzü uygulayabilirsin." Bu her dil için geçerli değil. Bazı diller çoklu sınıf mirasına izin verir.
Kenneth K.

Kenneth'in dediği gibi, iki popüler dilde çoklu kalıtım, C ++ ve Python var. threeArayüz kullanmamanın tuzakları örneğinizdeki sınıf aslında C ++ 'ta geçerlidir, örneğin aslında bu dört örnek için de düzgün çalışır. Aslında bu, C # 'ı, arayüzleri kullanırken miras almamaya dair uyarıcı bir masaldan ziyade bir dil olarak bir sınırlama gibi görünmektedir.
opa

63

Bu, polimorfizmin kullanılmasını önlemek için kullandığım bir şey.

NamedEntityMiras zincirinde bir yerde bir temel sınıf olan 15 farklı sınıfa sahip olduğunuzu ve yalnızca geçerli olan yeni bir yöntem yazdığınızı varsayalım OrderDateInfo.

İmzayı "imzalayabilirsin" gibi yazabilirsin.

void MyMethodThatShouldOnlyTakeOrderDateInfos(NamedEntity foo)

Üstelik kimsenin bu tip bir sistemi içine sokmak için kötüye kullanmasını umut ve dua edin FooBazNamedEntity.

Ya da sadece yazabilirsin void MyMethod(OrderDateInfo foo). Şimdi bu derleyici tarafından uygulanır. Sade, şık ve insanlara hata yapmamaya güvenmiyor.

Ayrıca, @candied_orange'in işaret ettiği gibi , istisnalar bunun çok iyi bir örneği. Çok nadiren (ve çok, çok, çok nadiren demek istiyorum) hiç bir şeyi yakalamak istemez misiniz catch (Exception e)? Muhtemelen , uygulamanız için bir SqlExceptionveya bir FileNotFoundExceptionveya özel istisna yakalamak istediğinizde . Bu sınıflar çoğu zaman temel Exceptionsınıftan daha fazla veri veya işlevsellik sağlamaz , ancak bunları incelemek ve bir tür alanını kontrol etmek veya belirli bir metni aramak zorunda kalmadan temsil ettiklerini farklılaştırmanıza izin verir.

Genel olarak, bir temel sınıf kullanıyor olmanız durumunda, alabileceğinizden daha dar türde bir dizi kullanmanıza izin vermek için type sistemini elde etmek bir püf noktasıdır. Demek istediğim, tüm değişkenlerinizi ve argümanlarınızı türüne sahip olarak tanımlayabilirsiniz Object, ancak bu işinizi zorlaştırabilir, değil mi?


4
Afedersiniz, fakat benim neden OrderDateInfo'yu bir NamedEntity nesnesi olarak bir özellik yapmak için daha iyi bir yaklaşım olmayacağını merak ediyorum.
Adam B

9
@AdamB Bu geçerli bir tasarım seçimi. Daha iyi olup olmaması diğer şeyler arasında neyin kullanılacağına bağlıdır. İstisna durumunda, istisna özelliğine sahip yeni bir sınıf oluşturamıyorum çünkü o zaman dil (benim için C #) atmama izin vermiyor. Kalıtım yerine kompozisyonun kullanılmasını engelleyebilecek başka tasarım konuları olabilir. Çoğu zaman kompozisyon daha iyidir. Bu sadece sisteminize bağlıdır.
Becuzz,

17
@AdamB Aynı nedenle, bir temel sınıftan miras aldığınızda ve DO, üsse sahip olmayan yöntemler veya özellikler eklediğinizde, yeni bir sınıf oluşturmaz ve üssü bir özellik olarak koyarsınız. Bir memeli İnsan ile bir memeli HAVING arasında bir fark var.
Logarr

8
@Logarr, benim bir memeli olmak ile bir memeliye sahip olmak arasında bir fark var. Ama iç memelime sadık kalırsam aradaki farkı asla bilemezsin. Bir hevesle neden bu kadar farklı türde süt verebileceğimi merak edebilirsiniz.
candied_orange

5
@AdamB Bunu yapabilirsiniz ve bu miras üzerinden kompozisyon olarak bilinir. Fakat bunun NamedEntityiçin, örneğin EntityName, tarif edildiğinde kompozisyonun mantıklı olması için yeniden adlandırırsınız .
Sebastian Redl

28

Bu, mirasın en sevdiğim kullanımı. Çoğunlukla daha iyi, daha belirgin isimler kullanabilecek istisnalar için kullanırım

Uzun zincirlere yol açan ve yo-yo sorununa neden olan genel miras sorunu burada geçerli değildir, çünkü sizi zincirlemeye motive edecek hiçbir şey yoktur.


1
Hmm, iyi nokta istisnalar. Yapmam gereken korkunç bir şey diyecektim. Ama beni mükemmel kullanım durumuyla başka türlü ikna ettin.
David Arno

Yo-Yo Ho ve bir şişe rom. Nadiren, orlop yarıktır. Tahtaların arasına uygun oakum istemek için sızdırıyor. Davey Jones'a, toprak yağlayıcıları olan dolabı, onu yapan şeydi. TERCÜME: Bir kalıtım zinciri o kadar iyidir ki, temel sınıf soyut / etkili bir şekilde göz ardı edilebilir.
radarbob

Onun 'için değer başka devralmasını yeni sınıf olduğunu işaret düşünüyorum gelmez yeni sınıfın adını - yeni bir özellik ekleyin. Bu, tam olarak daha iyi adlarla istisnalar oluştururken kullandığınız şeydir.
Logan Pickup

1

IMHO sınıf tasarımı yanlış. Olmalı.

public class EntityName
{
    public int Id { get; set; }
    public string Text { get; set; }
}

public class OrderDateInfo
{
    public EntityName Name { get; set; }
}

OrderDateInfoHAS A Namedaha doğal bir ilişkidir ve orijinal soruyu kışkırtmayacak şekilde anlaşılması kolay iki sınıf oluşturur.

NamedEntityParametre olarak kabul edilen herhangi bir yöntem yalnızca Idve Nameözellikleriyle ilgilenmiş olmalı , bu nedenle böyle bir yöntem kabul edilmek için değiştirilmelidir EntityName.

Orijinal tasarım için kabul edeceğim tek teknik sebep OP'nin bahsettiği mülk bağlamadır. Boktan bir çerçeve, ekstra mülkle başa çıkamaz ve ona bağlanamazdı object.Name.Id. Ancak, bağlayıcı çerçeveniz bununla baş edemiyorsa, listeye eklemek için daha fazla teknik borcunuz vardır.

@ Flater'ın cevabı ile devam ederdim, ama özellikleri içeren birçok arayüzle, C # 'ın güzel otomatik özellikleriyle bile, çok sayıda kazan plakası kodu yazıyorsunuz. Java'da yaptığını düşünün!


1

Sınıflar davranışı gösterir ve Veri Yapıları verileri gösterir.

Sınıf anahtar kelimelerini görüyorum ama hiçbir davranış göremiyorum. Bu, bu sınıfı veri yapısı olarak görmeye başlayacağım anlamına geliyor. Bu damarda, sorunuzu şu şekilde tekrarlayacağım:

Neden ortak bir üst düzey veri yapısına sahip?

Böylece en üst düzey veri türünü kullanabilirsiniz. Bu, özelliklerin hepsinin orada olmasını sağlayarak büyük farklı veri yapı kümeleri arasında bir politika sağlamak için tip sisteminin güçlendirilmesine izin verir.

Neden en üst seviyeyi içeren, ancak hiçbir şey ekleyen bir veri yapısı var?

Böylece alt seviye veri tipini kullanabilirsiniz. Bu, değişkenin amacını ifade etmek için yazma sistemine ipuçlarını koymanıza izin verir

Top level data structure - Named
   property: name;

Bottom level data structure - Person

Yukarıdaki hiyerarşide, bir Kişinin adının belirtilmesini uygun bulabiliriz; böylece insanlar adlarını alabilir ve değiştirebilir. PersonVeri yapısına ek özellikler eklemek makul olsa da, çözdüğümüz problem Kişinin mükemmel bir modellenmesini gerektirmez ve bu nedenle ortak özellikler agevb. Gibi ortak özellikler eklemeyi ihmal ettik .

Bu nedenle, bu Adlandırılmış öğenin amacını, güncelleştirmelerden kopmayacak şekilde (belgeler gibi) ve daha sonraki bir tarihte kolaylıkla genişletilebilecek şekilde (bu agealana gerçekten ihtiyacınız olduğunu görürseniz) genişletmek, yazma sisteminin güçlendirilmesidir. ).

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.