Kalıtım ve boş değerli ek mülk


12

İsteğe bağlı alanları olan sınıflar için, miras veya boş değerli bir özellik kullanmak daha mı iyidir? Bu örneği düşünün:

class Book {
    private String name;
}
class BookWithColor extends Book {
    private String color;
}

veya

class Book {
    private String name;
    private String color; 
    //when this is null then it is "Book" otherwise "BookWithColor"
}

veya

class Book {
    private String name;
    private Optional<String> color;
    //when isPresent() is false it is "Book" otherwise "BookWithColor"
}

Bu 3 seçeneğe bağlı olan kod:

if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }

veya

if (book.getColor() != null) { book.getColor(); }

veya

if (book.getColor().isPresent()) { book.getColor(); }

İlk yaklaşım benim için daha doğal görünüyor, ama belki de döküm yapma gereği nedeniyle daha az okunabilir. Bu davranışı elde etmenin başka bir yolu var mı?


1
Korkarım çözüm iş kuralları tanımınıza gelir. Demek istediğim, tüm kitaplarınızın rengi olabilir ancak rengi isteğe bağlıysa, kalıtım aşırıya kaçar ve aslında gereksizdir, çünkü tüm kitaplarınızın color özelliğini varolduğunu bilmelerini istersiniz. Öte yandan, hem renk hakkında hiçbir şey bilmeyen hem de renk olan kitaplara sahipseniz, özel sınıflar oluşturmak kötü değildir. O zaman bile, muhtemelen miras üzerinde kompozisyona giderdim.
Andy

Biraz daha spesifik hale getirmek için - biri renkli diğeri de olmadan iki tür kitap var. Yani tüm kitapların rengi olmayabilir.
Bojan VukasovicTest

1
Gerçekten, kitabın hiç rengi olmayan bir durum varsa, sizin durumunuzda miras, renkli bir kitabın özelliklerini nasıl genişleteceğinizin en basit yoludur. Bu durumda, temel kitap sınıfını devralarak ve ona bir color özelliği ekleyerek hiçbir şey kırmış gibi görünmüyorsunuz. Bir sınıfı genişletmeye izin verildiğinde ve izin verilmediğinde durumlar hakkında daha fazla bilgi edinmek için liskov ikame ilkesi hakkında bilgi edinebilirsiniz.
Andy

4
Renklendirilmiş kitaplardan istediğiniz bir davranışı tanımlayabilir misiniz? Renklendirilmiş ve renklendirilmemiş kitaplar arasında, renklendirilmiş kitapların biraz farklı davranması gereken ortak bir davranış bulabilir misiniz? Eğer öyleyse, bu, farklı tiplerde OOP için iyi bir durumdur ve fikir, mülk varlığını ve değeri harici olarak sorgulamak yerine davranışları sınıflara taşımaktır.
Erik Eidt

1
Olası kitap renkleri önceden biliniyorsa, BookColor.NoColor için bir seçenek içeren BookColor için bir Numaralandırma alanına ne dersiniz?
Graham

Yanıtlar:


7

Duruma göre değişir. BookWithColorGerçek bir programda adlandırılmış bir alt sınıfınız olmayacağından, bu örnek gerçekçi değildir .

Ancak genel olarak, sadece belirli alt sınıflar için anlamlı olan bir özellik sadece bu alt sınıflarda bulunmalıdır.

Örneğin eğer Booksahiptir PhysicalBookve DigialBooksoyundan olarak, daha sonra PhysicalBookbir olabilir weightözelliği ve DigitalBookbir sizeInKbözellik. Ama DigitalBookolmayacak weightve tam tersi. Bookbir sınıfın yalnızca tüm torunlar tarafından paylaşılan özelliklere sahip olması gerektiğinden, bu özelliklerin hiçbirinde özellik bulunmaz.

Daha iyi bir örnek, gerçek yazılımdan derslere bakmaktır. JSliderBileşen alanı vardır majorTickSpacing. Sadece bir kaydırıcının "keneleri" olduğundan, bu alan sadece JSliderve torunları için mantıklıdır . Gibi diğer kardeş bileşenleri JButtonbir majorTickSpacingalan olsaydı çok kafa karıştırıcı olurdu .


ya da her ikisini de yansıtabilmek için ağırlığı bastract edebilirsiniz, ama bu muhtemelen çok fazladır.
Walfrat

3
@Walfrat: Aynı alanın farklı alt sınıflarda tamamen farklı şeyleri temsil etmesi gerçekten kötü bir fikir olacaktır.
JacquesB

Bir kitabın hem fiziksel hem de dijital baskıları varsa ne olur? Her isteğe bağlı alana sahip olmak veya sahip olmamak için her permütasyon için farklı bir sınıf oluşturmanız gerekir. Bence en iyi çözüm, bazı alanların kitaba bağlı olarak atlanmasına izin vermemektir. İki sınıfın işlevsel olarak farklı davranacağı tamamen farklı bir durumdan bahsettiniz. Bu sorunun ima ettiği şey bu değil. Sadece bir veri yapısını modellemelidirler.
4castle

@ 4castle: Her sürümün farklı fiyatı olabileceğinden, sürüm başına ayrı sınıflara ihtiyacınız olacaktır. Bunu isteğe bağlı alanlarla çözemezsiniz. Ancak, op'un renkli veya "renksiz kitaplar" olan kitaplar için farklı davranışlar isteyip istemediğini bilmiyoruz, bu nedenle bu durumda doğru modellemenin ne olduğunu bilmek imkansız.
JacquesB

3
@JacquesB ile anlaşın. "kitap modelleme" için evrensel olarak doğru bir çözüm veremezsiniz, çünkü bu, hangi sorunu çözmeye çalıştığınıza ve işletmenizin ne olduğuna bağlıdır. Ben (evet evet, gerçi sen Liskov ihlal sınıf hiyerarşileri tanımlayarak kategorik Yanlış (tm) olduğunu söylemek oldukça güvenli olduğunu düşünüyorum sizin durumda muhtemelen çok özeldir ve garanti bunu (Sanmam rağmen))
sara

5

Bahsedilmemiş gibi önemli bir nokta: Çoğu dilde, bir sınıfın örnekleri, hangi sınıfın örneklendiklerini değiştiremez. Dolayısıyla, renksiz bir kitabınız varsa ve renk eklemek istiyorsanız, farklı sınıflar kullanıyorsanız yeni bir nesne oluşturmanız gerekir. Ve sonra muhtemelen eski nesneye yapılan tüm başvuruları yeni nesneye yapılan başvurularla değiştirmeniz gerekir.

"Renksiz kitaplar" ve "renksiz kitaplar" aynı sınıfın örnekleriyse, rengi eklemek veya rengi kaldırmak çok daha az sorun olacaktır. (Kullanıcı arayüzünüzde "renkli kitapların" ve "renksiz kitapların" bir listesi gösteriliyorsa, bu kullanıcı arayüzünün açıkça değişmesi gerekir, ancak bunun "kırmızı" bir listeye benzer şekilde ele almanız gereken bir şey olmasını beklerim kitaplar "ve" yeşil kitapların listesi ").


Bu gerçekten önemli bir nokta! Sınıf öznitelikleri yerine isteğe bağlı özelliklerin bir koleksiyonunu düşünüp düşünmeyeceğini tanımlar.
kromanoid

1

Bir JavaBean'ı (sizin gibi Book) veritabanındaki bir kayıt olarak düşünün . İsteğe bağlı sütunlar, nullhiçbir değerinin olmadığı zamandır, ancak tamamen yasaldır. Bu nedenle, ikinci seçeneğiniz:

class Book {
    private String name;
    private String color; // null when not applicable
}

En makul olanıdır. 1

OptionalSınıfı nasıl kullandığınıza dikkat edin . Örneğin Serializable, genellikle bir JavaBean'ın karakteristiği değildir . İşte Stephen Colebourne'dan bazı ipuçları :

  1. Herhangi bir örnek değişkeni bildirmeyin Optional.
  2. nullBir sınıfın özel kapsamındaki isteğe bağlı verileri belirtmek için kullanın .
  3. Optionalİsteğe bağlı alana erişen alıcılar için kullanın .
  4. OptionalAyarlayıcılarda veya yapıcılarda kullanmayın .
  5. Optionalİsteğe bağlı sonucu olan diğer iş mantığı yöntemleri için bir dönüş türü olarak kullanın .

Bu nedenle, içinde senin sınıfında kullanmanız gereken nullalan mevcut olmadığını temsil etmek, ama ne zaman color yapraklarıBook (dönüş türü gibi) bir ile sarılmalıdır Optional.

return Optional.ofNullable(color); // inside the class

book.getColor().orElse("No color"); // outside the class

Bu, net tasarım ve daha okunabilir kod sağlar.


1 Bir sizin için düşünüyorsanız BookWithColorkitapların bir bütün "sınıfı" enkapsüle etmek yetenekleri uzman diğer kitaplar üzerinde, daha sonra da kullanım miras mantıklıdır.


1
Bir veritabanı hakkında kim söyledi? Tüm OO kalıpları ilişkisel bir veritabanı paradigmasına uymak zorunda olsaydı, birçok OO kalıbını değiştirmek zorunda kalırdık.
alexanderbird

Ben "her ihtimale karşı" en az net seçenek kullanılmayan özellikler ekleyerek tartışmak istiyorum. Diğerlerinin söylediği gibi, kullanım durumuna bağlıdır, ancak en çok arzu edilen durum, harici bir uygulamanın, var olan bir mülkün gerçekten kullanılıp kullanılmadığını bilmesi gereken özellikleri taşımak değildir.
Volker Kueffel

@Volker Kabul etmemeyi kabul edelim, çünkü Optionalsınıfı bu amaçla Java'ya eklerken bir el oynayan birkaç kişiyi gösterebilirim . OO olmayabilir, ama kesinlikle geçerli bir görüş.
4castle

@Alexanderbird Özellikle serileştirilebilir olması gereken bir JavaBean ile uğraşıyordum. JavaBeans'i getirerek konu dışı kalsaydım, bu benim hatamdı.
4castle

@Alexanderbird Tüm desenlerin ilişkisel bir veritabanıyla eşleşmesini beklemiyorum, ancak bir Java Bean'in
4castle'ın

0

Bir arabirim (kalıtım değil) veya bir tür özellik mekaniği (belki de kaynak tanımlama çerçevesi gibi çalışan bir şey) kullanmalısınız.

Bu yüzden ya

interface Colored {
  Color getColor();
}
class ColoredBook extends Book implements Colored {
  ...
}

veya

class PropertiesHolder {
  <T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
  <V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
  String getName();
  T getValue();
}
class ColorProperty implements Property<Color> {
  public Color getValue() { ... }
  public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}

Açıklama (değiştir):

Varlık sınıfına yalnızca isteğe bağlı alanlar ekleme

Özellikle İsteğe bağlı sarıcı ile (değiştir: 4castle'ın cevabına bakınız ) Bence bu (orijinal varlığa alan eklemek) küçük ölçekli yeni özellikler eklemek için uygun bir yoldur. Bu yaklaşımla ilgili en büyük sorun, düşük bağlantıya karşı çalışabilmesidir.

Kitap sınıfınızın alan modeliniz için özel bir projede tanımlandığını düşünün. Şimdi özel bir görev yapmak için etki alanı modelini kullanan başka bir proje ekliyorsunuz. Bu görev, kitap sınıfında ek bir özellik gerektirir. Ya kalıtımla karşılaşırsınız (aşağıya bakın) ya da yeni görevi mümkün kılmak için ortak etki alanı modelini değiştirmeniz gerekir. İkinci durumda, kitap sınıfının kendisi bu projelere bağlıyken, hepsi kitap sınıfına eklenen kendi özelliklerine bağlı olan bir grup projeyle sonuçlanabilir, çünkü kitap sınıfını ek projeler.

Ek özellikler sağlamanın bir yolu söz konusu olduğunda miras neden sorunludur?

Örnek sınıf "Kitap" ı gördüğümde, genellikle birçok isteğe bağlı alan ve alt tipe sahip bir etki alanı nesnesini düşünüyorum. CD içeren kitaplar için bir özellik eklemek istediğinizi düşünün. Artık dört çeşit kitap var: kitaplar, renkli kitaplar, CD'li kitaplar, renkli kitaplar ve CD. Bu durumu Java'da kalıtımla tanımlayamazsınız.

Arabirimlerle bu sorunu atlatırsınız. Arabirimlerle belirli bir kitap sınıfının özelliklerini kolayca oluşturabilirsiniz. Yetki devri ve kompozisyon, tam olarak istediğiniz sınıfı almanızı kolaylaştıracaktır. Kalıtım ile genellikle sınıfta sadece orada olan bazı isteğe bağlı özellikler ile sonuçlanırsınız çünkü bir kardeş sınıfın onlara ihtiyacı vardır.

Mirasın neden genellikle sorunlu bir fikir olduğunu daha fazla okumak:

Kalıtım neden OOP taraftarları tarafından genellikle kötü bir şey olarak görülüyor?

JavaWorld: Neden uzanıyor kötülük

Bir dizi özellik oluşturmak için bir dizi arabirimin uygulanmasındaki sorun

Uzatma için arayüzler kullandığınızda, sadece küçük bir setiniz olduğu sürece her şey yolundadır. Özellikle nesne modeliniz, örneğin şirketinizdeki diğer geliştiriciler tarafından kullanılıp genişletildiğinde, arayüzlerin miktarı artacaktır. Ve sonunda, iş arkadaşlarınızın tamamen ilgisiz bir kullanım durumu için X müşterileri için projelerinde zaten kullandıkları bir yöntemi ekleyen yeni bir resmi "özellik arayüzü" oluşturursunuz - Ugh.

edit: Başka bir önemli yönü gnasher729 tarafından belirtilmiştir . Genellikle varolan bir nesneye dinamik olarak isteğe bağlı özellikler eklemek istersiniz. Kalıtım veya arabirimlerle, tüm nesneyi isteğe bağlı olmaktan başka bir sınıfla yeniden oluşturmanız gerekir.

Nesne modelinizin uzantılarını böyle bir ölçüde beklediğinizde, dinamik uzantı olasılığını açıkça modelleyerek daha iyi olursunuz . Her bir "uzantı" (bu durumda özelliği) kendi sınıfına sahip yukarıdaki gibi bir şey öneriyorum. Sınıf, uzantı için ad alanı ve tanımlayıcı olarak çalışır. Paket adlandırma kurallarını buna göre ayarladığınızda, orijinal varlık sınıfındaki yöntemler için ad alanını kirletmeden sonsuz miktarda uzantıya izin verir.

Oyun geliştirmede, genellikle birçok varyasyonda davranış ve veri oluşturmak istediğiniz durumlarla karşılaşırsınız. Bu nedenle mimari model varlık-bileşen-sistemi oyun geliştirme çevrelerinde oldukça popüler hale geldi. Bu, nesne modelinize birçok uzantı beklediğinizde, bakmak isteyebileceğiniz ilginç bir yaklaşımdır.


Ve "bu seçenekler arasında nasıl karar verileceği" sorusuna değinmedi, sadece başka bir seçenek. Bu neden OP'nin sunduğu tüm seçeneklerden her zaman daha iyidir?
alexanderbird

Haklısın, cevabı geliştireceğim.
kromanoid

@ 4castle Java arayüzleri kalıtım değildir! Arabirimleri uygulamak, bir sınıfı devralırken temelde davranışını genişletirken bir sözleşmeye uymanın bir yoludur. Bir sınıfta birden çok sınıfı genişletemezken birden çok arabirim uygulayabilirsiniz. Örneğimde, orijinal varlığın davranışını devralmak için miras kullanırken arabirimlerle genişletiyorsunuz.
kromanoid

Mantıklı. Örneğin, PropertiesHoldermuhtemelen soyut bir sınıf olurdu. Ama için bir uygulama olması neyi öneriyorsun getPropertyya getPropertyValue? Veriler hala bir tür örnek alanında saklanmalıdır. Veya Collection<Property>alanları kullanmak yerine özellikleri depolamak için a kullanır mısınız?
4castle

1
Anlaşılır olması için Bookgenişletme yaptım PropertiesHolder. PropertiesHolderGenellikle bu işlevi gereken tüm sınıflarda üyesi olmalıdır. Uygulama Map<Class<? extends Property<?>>,Property<?>>böyle bir şeye sahip olacaktı . Bu uygulamanın çirkin bir parçası ama JAXB ve JPA ile bile çalışan güzel bir çerçeve sunuyor.
kromanoid

-1

Eğer Booksınıf aşağıda gibi renk özelliği olmadan derive edilir

class E_Book extends Book{
   private String type;
}

o zaman kalıtım makuldür.


Örneğini anladığımdan emin değilim? colorÖzel ise , türetilmiş herhangi bir sınıfın coloryine de ilgilenmesi gerekmez . Bunu tamamen Bookyok saymak için bazı yapıcılar ekleyin colorve varsayılan olarak ayarlanacaktır null.
4castle
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.