Kalıtım ve Toplama Karşılaştırması [kapalı]


151

Nesneye yönelik bir sistemde kodu en iyi şekilde nasıl genişleteceğiniz, geliştireceğiniz ve yeniden kullanacağınız konusunda iki düşünce okulu vardır:

  1. Kalıtım: bir alt sınıf oluşturarak sınıfın işlevselliğini arttırır. Yeni işlevler sağlamak için alt sınıflardaki üst sınıf üyelerini geçersiz kılın. Üst sınıf belirli bir arabirim istediğinde ancak uygulaması konusunda agnostik olduğunda alt sınıfları "boşlukları doldurmaya" zorlamak için yöntemleri soyut / sanal yapın.

  2. Toplama: diğer sınıfları alıp yeni bir sınıfta birleştirerek yeni işlevler oluşturun. Diğer kodlarla birlikte çalışabilmek için bu yeni sınıfa ortak bir arabirim ekleyin.

Her birinin faydaları, maliyetleri ve sonuçları nelerdir? Başka alternatifler var mı?

Bu tartışmanın düzenli olarak gerçekleştiğini görüyorum, ancak bunun henüz Stack Overflow'da sorulduğunu sanmıyorum (ancak ilgili bir tartışma olmasına rağmen). Bunun için iyi Google sonuçlarının şaşırtıcı bir eksikliği de var.


9
Güzel soru, maalesef şu anda yeterli zamanım yok.
Toon Krijthe

4
İyi bir yanıt daha hızlı olandan daha iyidir ... Kendi sorumu izleyeceğim, bu yüzden sana en azından oy vereceğim :-P
Craig Walker

Yanıtlar:


181

Hangisinin en iyisi olduğu değil, ne zaman kullanılacağı meselesi.

'Normal' vakalarda, miras veya toplamaya ihtiyacımız olup olmadığını anlamak için basit bir soru yeterlidir.

  • Yeni sınıf ise olduğu orijinal sınıf olarak az ya da çok. Kalıtım kullanın. Yeni sınıf, artık orijinal sınıfın bir alt sınıfıdır.
  • Yeni sınıfın orijinal sınıfa sahip olması gerekir . Toplama kullanın. Yeni sınıf artık üye olarak orijinal sınıfa sahiptir.

Ancak, büyük bir gri alan var. Bu yüzden başka numaralara ihtiyacımız var.

  • Kalıtım kullandıysak (veya kullanmayı planlıyoruz) ancak arayüzün yalnızca bir kısmını kullanırsak veya korelasyonu mantıklı tutmak için birçok işlevselliği geçersiz kılmak zorunda kalırız. Sonra kümelenme kullanmak zorunda olduğumuzu gösteren büyük bir kötü kokumuz var.
  • Toplama kullandıysanız (veya kullanmayı planlıyorsak), ancak neredeyse tüm işlevleri kopyalamamız gerektiğini öğreniriz. Sonra kalıtım yönünü gösteren bir kokumuz var.

Kısa kesmek için. Arayüzün bir kısmı kullanılmıyorsa veya mantıksız bir durumdan kaçınmak için değiştirilmesi gerekiyorsa toplama kullanmalıyız. Büyük değişiklikler olmadan neredeyse tüm işlevselliklere ihtiyacımız varsa, yalnızca kalıtım kullanmamız gerekir. Şüphe duyduğunuzda Toplama'yı kullanın.

Diğer bir olasılık da, orijinal sınıfın işlevselliğinin bir kısmına ihtiyaç duyan bir sınıfa sahip olmamız durumunda, orijinal sınıfı bir kök sınıfında ve bir alt sınıfta bölmektir. Ve yeni sınıfın kök sınıftan miras almasına izin verin. Ama mantıksız bir ayrılık yaratmak için değil, bununla ilgilenmelisin.

Bir örnek ekleyelim. 'Eat', 'Walk', 'Bark', 'Play' yöntemleriyle bir 'Dog' sınıfımız var.

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

Şimdi 'Eat', 'Walk', 'Purr' ve 'Play' gerektiren bir 'Cat' sınıfına ihtiyacımız var. Bu yüzden önce onu bir Köpek'ten uzatmaya çalışın.

class Cat is Dog
  Purr; 
end;

Görünüyor, tamam, ama bekle. Bu kedi Bark yapabilir (Kedi severler bunun için beni öldürecek). Ve havlayan bir kedi evrenin ilkelerini ihlal eder. Bu yüzden Bark yöntemini geçersiz kılmalıyız, böylece hiçbir şey yapmaz.

class Cat is Dog
  Purr; 
  Bark = null;
end;

Tamam, bu işe yarıyor, ama kötü kokuyor. Şimdi bir toplamayı deneyelim:

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

Tamam, bu güzel. Bu kedi artık havlamıyor, sessiz bile değil. Ama yine de dışarı isteyen bir köpeği var. Üç numaralı çözümü deneyelim:

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

Bu çok daha temiz. İç köpek yok. Ve kediler ve köpekler aynı seviyededir. Modeli genişletmek için diğer evcil hayvanları bile tanıtabiliriz. Bir balık ya da yürümeyen bir şey olmadığı sürece. Bu durumda tekrar gözden geçirmemiz gerekiyor. Ama bu başka bir zaman için bir şey.


5
"Sınıfın neredeyse tüm işlevlerini yeniden kullanma" kalıtım tercih ettiğim tek seferdir. Gerçekten istediğim , kolayca "bu özel yöntemler için bu toplu nesneye delege" deme yeteneğine sahip bir dildir; bu her iki dünyanın en iyisi.
Craig Walker

Diğer dillerden emin değilim, ama Delphi'nin üyelerin arayüzün bir kısmını uygulamasına izin veren bir mekanizması var.
Toon Krijthe

2
Amaç C, fonksiyonunuzun gerekli veya isteğe bağlı olup olmadığına karar vermenize izin veren Protokolleri kullanır.
cantfindaname88

1
Pratik bir örnekle harika cevap. Ben Pet sınıf denilen bir yöntemle tasarlar makeSoundve her alt sınıf kendi ses türlerini uygulamak izin. Bu daha sonra birçok evcil hayvanınızın olduğu ve sadece sahip olduğunuz durumlarda yardımcı olacaktır for_each pet in the list of pets, pet.makeSound.
blongho

38

Başında GOF onlar devlet

Nesne kompozisyonunu sınıf mirasına tercih edin.

Bu burada daha ayrıntılı tartışılmaktadır


27

Fark tipik olarak "bir" dir ve "bir" vardır "olarak ifade edilir. Kalıtım, "bir" ilişkidir, Liskov İkame Prensibi'nde güzel bir şekilde özetlenir . Toplama, "bir" ilişkiye sahiptir, tam da budur - toplama nesnesinin birleştirilmiş nesnelerden birine sahip olduğunu gösterir .

Diğer ayrımlar da vardır - C ++ 'daki özel kalıtım, "açıklanamayan" üye nesnelerin toplanmasıyla da modellenebilen bir "terimlerle uygulanmaktadır" ilişkisini gösterir.


Ayrıca, yanıtlamanız gereken soruda fark güzelce özetlenir. İnsanların çeviri yapmaya başlamadan önce soruları okumasını istiyorum. Seçkin kulübün bir üyesi olmak için tasarım kalıplarını körü körüne tanıtıyor musunuz?
Val

Bu yaklaşımı sevmiyorum, daha çok kompozisyonla ilgili
Alireza Rahmani Khalili

15

İşte benim en yaygın argümanım:

Herhangi bir nesne yönelimli sistemde, herhangi bir sınıfın iki bölümü vardır:

  1. Onun arayüzü : nesnenin "kamu yüzü". Bu, dünyanın geri kalanına duyurduğu yetenekler kümesidir. Birçok dilde, küme "sınıf" olarak iyi tanımlanmıştır. Genellikle bunlar nesnenin yöntem imzalarıdır, ancak dile göre biraz değişir.

  2. Onun uygulanması : nesne arayüzü tatmin ve işlevsellik sağlamak için yaptığı "perde arkasında" çalışması. Bu genellikle nesnenin kodu ve üye verileridir.

OOP'nin temel ilkelerinden biri, uygulamanın sınıf içinde kapsüllenmiş (yani: gizli); yabancıların görmesi gereken tek şey arayüz.

Bir alt sınıf bir alt sınıftan miras alırsa, genellikle hem uygulamayı hem de arabirimi devralır . Bu da, her ikisini de sınıfınızda bir kısıtlama olarak kabul etmek zorunda olduğunuz anlamına gelir .

Toplama ile, uygulama veya arabirimi veya her ikisini birden seçersiniz - ancak ikisinden birine zorlanmazsınız. Bir nesnenin işlevselliği nesnenin kendisine bırakılmıştır. Diğer nesnelere istediği gibi erteleyebilir, ancak sonuçta kendisinden sorumludur. Deneyimlerime göre, bu daha esnek bir sisteme yol açar: değiştirilmesi daha kolay bir sistem.

Nesneye yönelik yazılımlar geliştirdiğimde, neredeyse her zaman miras yerine toplamayı tercih ederim.


Bir arabirim tanımlamak için soyut bir sınıf kullandığınızı ve sonra her somut uygulama sınıfı için doğrudan kalıtım kullandığınızı düşünürüm (oldukça sığ hale getirir). Herhangi bir toplama somut uygulama kapsamındadır.
orcmid

Bundan daha fazla arabirime ihtiyacınız varsa, bunları ana düzey soyutlanmış arabirimin arabirimindeki yöntemlerle döndürün, tekrarlayın.
orcmid

Sizce bu senaryoda toplama daha iyi mi? Bir kitap bir SalesItem, bir DigitalDisc bir SelliingItem codereview.stackexchange.com/questions/14077/…
LCJ

11

"Bir" mi "var mı" sorusuna cevap verdim : hangisi daha iyi? .

Temelde ben diğer millet ile anlaşmak: En türetilmiş sınıf gerçekten sadece kullanım miras olduğunu öyle olmadığını sırf, uzanan ediyoruz tipi içeren aynı verileri. Kalıtımın, alt sınıfın verilerin yanı sıra yöntemleri de kazandığı anlamına geldiğini unutmayın.

Türetilmiş sınıfınızın üst sınıfın tüm yöntemlerine sahip olması mantıklı mı? Yoksa bu yöntemlerin türetilmiş sınıfta göz ardı edilmesi gerektiğini sessizce mi vaat ediyorsunuz? Yoksa kendinizi üst sınıftan geçersiz kılan yöntemler buluyor musunuz, hiç kimsenin onları istemeden çağırmaması mı? Ya da yöntemi doc'den atlamak için API doc oluşturma aracınıza ipuçları mı veriyorsunuz?

Bunlar, kümelenmenin bu durumda daha iyi bir seçim olduğuna dair güçlü ipuçlarıdır.


6

Bu ve ilgili sorularda birçok "is-a ve has-a; kavramsal olarak farklılar" yanıtları görüyorum.

Deneyimlerime göre bulduğum tek şey, bir ilişkinin "is-a" ya da "has-a" olup olmadığını belirlemeye çalışmanın başarısızlığa uğramasıdır. Nesneleri şu anda doğru bir şekilde belirleyebilseniz bile, değişen gereksinimler muhtemelen gelecekte bir noktada yanlış olacağınız anlamına gelir.

Bulduğum bir başka şey, bir miras hiyerarşisi etrafında yazılmış çok fazla kod olduğunda mirastan toplamaya dönüştürmenin çok zor olduğudur. Bir üst sınıftan bir arabirime geçmek, sistemdeki hemen hemen her alt sınıfı değiştirmek anlamına gelir.

Ve, bu yazının başka bir yerinde de belirttiğim gibi, toplama, kalıtımdan daha az esnek olma eğilimindedir.

Yani, birini veya diğerini seçmeniz gerektiğinde kalıtıma karşı mükemmel bir tartışma fırtınasına sahipsiniz:

  1. Seçiminiz bir noktada yanlış olacaktır
  2. Bunu yaptıktan sonra bu seçimi değiştirmek zordur.
  3. Kalıtım, daha kısıtlayıcı olduğu için daha kötü bir seçim olma eğilimindedir.

Bu nedenle, kümelenmeyi seçme eğilimindeyim - güçlü bir ilişki olduğu görülse bile.


Gelişen gereksinimler OO tasarımınızın kaçınılmaz olarak yanlış olacağı anlamına gelir. Miras ve kümelenme, bu buzdağının sadece görünen kısmıdır. Mümkün olan tüm gelecekler için mimarlık yapamazsınız, bu yüzden XP fikrini kullanın: bugünün gereksinimlerini çözün ve yeniden düzenleme yapmanız gerekebileceğini kabul edin.
Bill Karwin

Bu sorgulama ve sorgulamayı seviyorum. Blurrung is-a, has-a ve kullanımları-a hakkında endişeli. Arayüzlerle başlıyorum (arayüzlerin olmasını istediğim uygulanan soyutlamaları tanımlamanın yanında). Ek dolaylama seviyesi çok değerlidir. Toplama sonucunuzu takip etmeyin.
orcmid

Gerçi bu benim açımdan çok. Hem kalıtım hem de toplama sorunu çözmek için kullanılan yöntemlerdir . Yarın sorunları çözmek zorunda olduğunuzda, bu yöntemlerden biri her türlü cezaya neden olur.
Craig Walker

Aklımda, toplama + arayüzler kalıtım / alt sınıflamaya alternatiftir; yalnız toplanmaya dayalı polimorfizm yapamazsınız.
Craig Walker


3

Bunu orijinal soruya yorum yapmak istedim, ancak 300 karakter ısırdı [; <).

Bence dikkatli olmalıyız. İlk olarak, soruda yapılan iki spesifik örnekten daha fazla lezzet vardır.

Ayrıca, hedefi araçla karıştırmamanın değerli olduğunu da öneriyorum. Seçilen tekniğin veya metodolojinin birincil amacın gerçekleştirilmesini desteklediğinden emin olmak istiyor, ancak hangi tekniğin en iyi olduğu bağlam dışı tartışmanın çok yararlı olduğunu bilmiyorum. Onların net tatlı noktaları ile birlikte farklı yaklaşımların tuzaklarını bilmek yardımcı olur.

Örneğin, neyi başarmak için dışarıdasınız, başlamak için neye sahipsiniz ve kısıtlamalar nelerdir?

Bileşen çerçevesi, hatta özel amaçlı bir çerçeve oluşturuyor musunuz? Arabirimler programlama sistemindeki uygulamalardan ayrı mıdır yoksa farklı türde bir teknoloji kullanan bir uygulama ile mi gerçekleştirilmektedir? Arabirimlerin (varsa) kalıtım yapısını, bunları uygulayan sınıfların kalıtım yapısından ayırabilir misiniz? Bir uygulamanın sınıf yapısını, uygulamanın sağladığı arabirimlere dayanan koddan gizlemek önemli mi? Aynı anda kullanılabilecek birden fazla uygulama var mı veya bakım ve geliştirmenin bir sonucu olarak varyasyon zaman içinde daha mı fazla? Bir araca veya metodolojiye sabitlemeden önce bu ve daha fazlasının dikkate alınması gerekir.

Son olarak, soyutlamadaki ayrımları ve düşüncelerinizi OO teknolojisinin farklı özelliklerine (olduğu gibi bir-has-a'ya benzer) kilitlemek önemli mi? Belki de, kavramsal yapıyı siz ve diğerleri için tutarlı ve yönetilebilir tutarsa. Ama buna ve sonuçta elde edebileceğiniz sarsıntılara köleleştirilmemek akıllıca olacaktır. Belki bir seviyenin arkasında durmak ve bu kadar katı olmamak en iyisidir (ama başkalarının ne olduğunu söyleyebilmesi için iyi bir anlatım bırakın). [Bir programın belirli bir bölümünü açıklanabilir kılan şeyleri arıyorum, ancak bazen daha büyük bir kazanç olduğunda zarafet ararım. Her zaman en iyi fikir değil.]

Ben bir arayüz sadeciyim ve ister bir Java çerçevesi oluşturuyor ister bazı COM uygulamaları düzenliyor olun, arayüz saflığının uygun olduğu sorun ve yaklaşım türlerine ilgi duyuyorum. Yemin etsem bile, her şeye uygun değil, her şeye yakın bile değil. (Arayüz saflığına karşı ciddi karşı örnekler verdiği anlaşılan birkaç projem var, bu yüzden nasıl başa çıkabileceğimi görmek ilginç olacak.)


2

Nerede uygulanabileceği kısmını ele alacağım. İşte bir oyun senaryosunda her ikisine de bir örnek. Diyelim ki farklı türde askerler olan bir oyun var. Her askerin farklı şeyleri tutabilen bir sırt çantası olabilir.

Burada miras? Deniz, yeşil bere ve keskin nişancı var. Bunlar asker türleri. Yani, türetilmiş sınıflar olarak Marine, Green Beret & Sniper ile bir temel sınıf Asker

Burada toplanma? Sırt çantası el bombaları, silahlar (farklı tipler), bıçak, medikit, vb. yaralanma belirli bir yüzdeye düşer. Asker sınıfı kurşun geçirmez yelek sınıfı bir nesne ve bu öğelere referanslar içeren sırt çantası sınıfı içerir.


2

Bence bu bir ya da tartışma değil. Sadece o:

  1. is-a (kalıtım) ilişkileri has-a (kompozisyon) ilişkilerinden daha az görülür.
  2. Kalıtımın doğru kullanılması daha zordur, kullanılmaya uygun olsa bile, bu nedenle kapsüllenmeyi kırabileceğinden, uygulamayı açığa vurarak sıkı bağlantıyı teşvik ettiğinden dolayı gereken özen gösterilmelidir.

Her ikisinin de yeri var, ama miras daha risklidir.

Tabii ki bir Point Shape 'a sahip olmak ve Point ve Square sınıflarına sahip olmak anlamlı olmaz. İşte kalıtım zamanı.

İnsanlar, uzatılabilir bir şey tasarlamaya çalışırken ilk olarak miras düşünmeye eğilimlidir, yanlış olan budur.


1

Her iki aday da hak kazanır. A ve B seçeneklerdir ve A'yı tercih edersiniz. Bunun nedeni, kompozisyonun genellemeden daha fazla genişletme / esneklik olanakları sunmasıdır. Bu genişletme / esneklik, çoğunlukla çalışma zamanı / dinamik esneklik anlamına gelir.

Fayda hemen görünmez. Avantajı görmek için bir sonraki beklenmedik değişiklik isteğini beklemeniz gerekir. Bu nedenle, çoğu durumda, genelleştirmeye bağlı olanlar, kompozisyonu benimseyenlerle karşılaştırıldığında başarısız olur (daha sonra bahsedilen açık bir vaka hariç). Dolayısıyla kural. Bir öğrenme açısından bakıldığında, bir bağımlılık enjeksiyonunu başarılı bir şekilde uygulayabiliyorsanız, hangisinin hangisini tercih edeceğini bilmelisiniz. Kural, karar vermenize de yardımcı olur; Eğer emin değilseniz kompozisyon seçin.

Özet: Kompozisyon: Bağlama sadece daha büyük bir şeye taktığınız bazı küçük şeylere sahip olarak azaltılır ve büyük nesne daha küçük nesneyi geri çağırır. Genelleştirme: Bir yöntemin geçersiz kılınabileceğini tanımlayan bir API açısından, bir yöntemin çağrılabileceğini tanımlamaktan daha güçlü bir taahhüttür. (Genelleme kazandığında çok az olay). Ve asla kompozisyon ile miras kullandığınızı, büyük bir sınıf yerine bir arayüzden kullandığınızı asla unutmayın


0

Her iki yaklaşım da farklı sorunları çözmek için kullanılır. Bir sınıftan miras alırken her zaman iki veya daha fazla sınıfı birleştirmeniz gerekmez.

Bazen tek bir sınıfı bir araya getirmeniz gerekir, çünkü o sınıf mühürlenir veya başka bir şekilde sanal olmayan üyelere sahip olmanız gerekir, böylece miras açısından geçerli olmayan, ancak proxy uyguladığınız sürece bir proxy katmanı oluşturursunuz. abone olabilir bir arayüze sahip bu oldukça iyi çalışabilir.

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.