Yanıtlar:
Çoklu kalıtım (MI olarak kısaltılır) kokuyor , bu da genellikle kötü nedenlerle yapıldığı ve bakımcının karşısında geri uçacağı anlamına gelir .
Bu kalıtım için geçerlidir ve bu nedenle çoklu kalıtım için daha da doğrudur.
Nesnenin gerçekten bir başkasından miras alması gerekiyor? A'nın Car
bir Engine
işten ya da a'dan miras alması gerekmez Wheel
. A'nın Car
bir Engine
ve dört vardır Wheel
.
Kompozisyon yerine bu sorunları çözmek için birden fazla miras kullanıyorsanız, yanlış bir şey yaptınız.
Genellikle, bir sınıf var A
, sonra B
ve C
her iki devralma A
. Ve sonra (neden bana sorma) birisi D
hem B
ve hem de miras alması gerektiğine karar verir C
.
Sekiz yılda iki kez bu tür bir sorunla karşılaştım ve aşağıdakilerden dolayı görmek eğlenceli:
D
miras almamalıydım B
ve C
), çünkü bu kötü bir mimariydi (aslında C
hiç var olmamalıydı ...)A
torun sınıfında iki kez mevcuttu D
ve bu nedenle, bir ana alanın A::field
güncellenmesi , ya iki kez (üzerinden B::field
ve C::field
) güncellenmesi ya da bir şeyin sessizce yanlış gitmesi ve çökmesi anlamına geliyordu , (yeni bir işaretçi girin B::field
ve silin C::field
...)Miras nitelendirmek için C ++ sanal anahtar sözcüğünü kullanmak, bu istediğiniz şey değilse yukarıda açıklanan çift mizanpajdan kaçınır, ancak yine de, deneyimime göre, muhtemelen yanlış bir şey yapıyorsunuz ...
Nesne hiyerarşisinde, hiyerarşiyi grafik olarak değil, bir Ağaç (düğümün BİR üst öğesi vardır) olarak tutmaya çalışmalısınız.
Diamond of Dread in C ++ ile ilgili asıl sorun ( tasarımın ses olduğu varsayılırsa - kodunuzu gözden geçirin! ), Bir seçim yapmanız gerektiğidir :
A
mizanpajınızda iki kez olması arzu edilir mi ve bu ne anlama geliyor? Evet ise, elbette ondan iki kez miras alın.Bu seçim sorunun doğasında vardır ve C ++ 'da, diğer dillerden farklı olarak, tasarımınızı dil düzeyinde zorlamaya gerek kalmadan yapabilirsiniz.
Ancak tüm güçler gibi, bu güçle sorumluluk gelir: Tasarımınızı gözden geçirin.
Sıfır veya bir beton sınıfının ve sıfır veya daha fazla arayüzün çoklu kalıtımı genellikle iyidir, çünkü yukarıda açıklanan Dread Diamond ile karşılaşmazsınız. Aslında, Java'da işler böyle yapılır.
Genellikle, C devralındığında A
ve B
kullanıcıların bir C
sanki a A
ve / veya sanki a B
.
C ++ 'da, bir arayüz aşağıdakileri içeren soyut bir sınıftır:
Sıfırın bir gerçek nesneye ve sıfır veya daha fazla arabirime Çoklu kalıtım "koklamak" olarak kabul edilmez (en azından, o kadar değil).
Birincisi, NVI deseni bir arayüz üretmek için kullanılabilir, çünkü gerçek kriterler hiçbir durum içermemelidir (yani üye değişkenler hariç this
). Soyut arayüzünüzün amacı bir sözleşme yayınlamaktır ("beni bu şekilde ve bu şekilde arayabilirsiniz"), başka bir şey, hiçbir şey daha az. Sadece soyut sanal yönteme sahip olmanın sınırlandırılması bir yükümlülük değil bir tasarım seçimi olmalıdır.
İkincisi, C ++ 'da, soyut arayüzlerden neredeyse miras almak mantıklıdır (ek maliyet / dolaylı olsa bile). Yapmazsanız ve arayüz devralma hiyerarşinizde birden çok kez görünürse, belirsizlikler yaşarsınız.
Üçüncüsü, nesne yönelimi harika, ancak C ++ 'daki Tek Gerçek TM değil . Doğru araçları kullanın ve C ++ 'da farklı türde çözümler sunan başka paradigmalarınız olduğunu her zaman unutmayın.
Bazen evet.
Genellikle, C
sınıf devralmakta A
ve B
ve A
ve B
(yani değil aynı hiyerarşide ortak, farklı kavramlar vs. hiçbir şey) iki ilgisiz nesneleri vardır.
Örneğin, Nodes
X, Y, Z koordinatlarına sahip bir sisteminiz olabilir, çok fazla geometrik hesaplamalar yapabilir (belki bir nokta, geometrik nesnelerin bir parçası) ve her Düğüm diğer ajanlarla iletişim kurabilen bir Otomatik Ajandır.
Belki zaten her biri kendi ad alanına sahip iki kütüphaneye erişiminiz vardır (isim alanlarını kullanmanın başka bir nedeni ... Ama isim alanlarını kullanıyorsunuz, değil mi?), Biri varlık geo
ve diğeriai
Böylece own::Node
hem ai::Agent
ve ' den kendi türetmeniz var geo::Point
.
Bu, kendinize bunun yerine kompozisyon kullanmamayı sormanız gereken andır. Eğer own::Node
gerçekten bir hem ai::Agent
bir geo::Point
, daha sonra kompozisyon yapmayacağım.
Ardından, own::Node
3B alandaki konumlarına göre diğer ajanlarla iletişim kurmanız için birden fazla mirasa ihtiyacınız olacak .
(Bunu not edeceksiniz ai::Agent
ve geo::Point
tamamen, tamamen, tamamen İLGİLİ OLMAYIN ... Bu, çoklu kalıtım tehlikesini büyük ölçüde azaltır)
Başka durumlar da var:
this
)Bazen kompozisyon kullanabilirsiniz ve bazen MI daha iyidir. Mesele şu: Bir seçeneğiniz var. Sorumlu bir şekilde yapın (ve kodunuzu gözden geçirin).
Çoğu zaman, deneyimlerime göre, hayır. MI o kazık tembel tarafından kullanılabilir çünkü (a yapmak gibi sonuçlarının da farkında olmadan birlikte özellikleri, iş gibi görünüyor olsa bile, doğru araç değildir Car
hem bir Engine
ve Wheel
).
Ama bazen, evet. Ve o zaman, hiçbir şey MI'dan daha iyi çalışmayacak.
Ancak MI koklamakta olduğundan, kod incelemelerinde mimarinizi savunmaya hazır olun (ve savunmak iyi bir şeydir, çünkü savunamazsanız, o zaman yapmamalısınız).
Bjarne Stroustrup ile yapılan bir röportajdan :
İnsanlar oldukça doğru bir şekilde çoklu mirasa ihtiyacınız olmadığını söylüyorlar, çünkü çoklu miras ile yapabileceğiniz her şeyi tek miras ile de yapabilirsiniz. Sadece bahsettiğim delegasyon hilesini kullan. Ayrıca, herhangi bir mirasa ihtiyacınız yoktur, çünkü tek mirasla yaptığınız her şey, bir sınıf boyunca ileterek miras olmadan da yapabilirsiniz. Aslında, herhangi bir sınıfa da ihtiyacınız yok, çünkü her şeyi işaretçiler ve veri yapılarıyla yapabilirsiniz. Ama neden bunu yapmak istesin? Dil olanaklarını kullanmak ne zaman uygundur? Ne zaman bir çözümü tercih ederdiniz? Çoklu kalıtımın yararlı olduğu vakaları gördüm ve hatta oldukça karmaşık çoklu kalıtımın yararlı olduğu vakaları gördüm. Genellikle, dilin sunduğu olanakları geçici çözüm olarak kullanmayı tercih ederim
const
- yaşadım aksak geçici çözümler yazmak için (genellikle arayüzleri ve kompozisyon kullanarak) bir sınıf gerçekten zaman gerekli mutable- sahip olmak ve-değişmez varyantları. Ancak, bir zamanlar birden fazla miras kaçırmadım ve bu özelliğin olmaması nedeniyle bir geçici çözüm yazmak zorunda olduğumu hiç hissetmedim. Aradaki fark bu. Her durumda şimdiye kadar, gördüğüm değil MI daha iyi tasarım seçimi değil, bir geçici çözüm olduğunu kullanılarak.
Bundan kaçınmak için bir neden yoktur ve bazı durumlarda çok yararlı olabilir. Yine de olası sorunların farkında olmanız gerekir.
En büyüğü ölüm elmasıdır:
class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;
Artık Child içinde GrandParent'in iki "kopyası" nız var.
C ++ bunu düşünmüş ve sorunları aşmak için sanal kalıtım yapmanıza izin veriyor.
class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;
Daima tasarımınızı gözden geçirin, verilerin yeniden kullanılmasından tasarruf etmek için kalıtım kullanmadığınızdan emin olun. Kompozisyon ile aynı şeyi temsil edebilirseniz (ve tipik olarak yapabilirsiniz) bu çok daha iyi bir yaklaşımdır.
GrandParent
var Child
. MI korkusu var, çünkü insanlar sadece dil kurallarını anlamayabileceklerini düşünüyorlar. Ancak bu basit kuralları alamayan herkes de önemsiz olmayan bir program yazamaz.
Bkz. W: Çoklu Kalıtım .
Birden fazla miras eleştiri aldı ve bu nedenle birçok dilde uygulanmadı. Eleştiriler şunları içerir:
- Artan karmaşıklık
- Anlamsal belirsizlik genellikle elmas problemi olarak özetlenir .
- Tek bir sınıftan birden çok kez miras kalmamak
- Miras sırasını değiştiren sınıf anlambilimi.
C ++ / Java stili yapıcıları olan dillerde çoklu kalıtım, kurucuların ve kurucu zincirinin miras sorununu daha da şiddetlendirir, böylece bu dillerde bakım ve genişletilebilirlik sorunları yaratır. Çok çeşitli inşaat yöntemleriyle miras ilişkilerindeki nesnelerin, yapıcı zincirleme paradigması altında uygulanması zordur.
COM ve Java arayüzü gibi arayüz (saf soyut sınıf) kullanmak için bunu modern bir yol.
Bunun yerine başka şeyler yapabilir miyim?
Evet yapabilirsin. GoF'den çalarım .
Kamusal miras bir IS-A ilişkisidir ve bazen bir sınıf birkaç farklı sınıf türü olabilir ve bazen bunu yansıtmak önemlidir.
"Karışımlar" da bazen yararlıdır. Genellikle küçük sınıflardır, genellikle hiçbir şeyden miras kalmazlar, kullanışlı işlevsellik sağlarlar.
Kalıtım hiyerarşisi oldukça sığ olduğu (neredeyse her zaman olması gerektiği gibi) ve iyi yönetildiği sürece, korkunç elmas mirasını almanız olası değildir. Elmas, birden fazla miras kullanan tüm dillerde bir sorun oluşturmaz, ancak C ++ 'ın tedavisi genellikle garip ve bazen kafa karıştırıcıdır.
Birden fazla mirasın çok kullanışlı olduğu durumlarla karşılaşsam da, aslında oldukça nadirdirler. Bunun nedeni, birden fazla mirasa gerçekten ihtiyaç duymadığım zamanlarda diğer tasarım yöntemlerini kullanmayı tercih etmemdir. Dil yapılarını karıştırmaktan kaçınmayı tercih ediyorum ve neler olduğunu anlamak için el kitabını gerçekten iyi okumak zorunda olduğunuz miras vakaları oluşturmak kolaydır.
Birden fazla mirastan "kaçınmalısınız" ama 'elmas problemi' ( http://en.wikipedia.org/wiki/Diamond_problem ) gibi ortaya çıkabilecek sorunların farkında olmalı ve size verilen gücü dikkatli bir şekilde ele almalısınız. , tüm güçlerde olması gerektiği gibi.
Biraz soyut olma riski altında, kategori teorisi çerçevesinde kalıtım hakkında düşünmeyi aydınlatıcı buluyorum.
Tüm sınıflarımızı ve aralarındaki miras ilişkilerini gösteren okları düşünürsek, böyle bir şey
A --> B
aracı class B
türemiştir class A
. Unutmayın, verilen
A --> B, B --> C
C'nin A'dan türeyen B'den türediğini söylüyoruz, bu nedenle C'nin A'dan türediği de söyleniyor,
A --> C
Ayrıca, A
önemsiz bir şekilde A
türetilmiş her sınıf için A
, kalıtım modelimizin bir kategori tanımını yerine getirdiğini söylüyoruz . Daha geleneksel bir dilde, Class
tüm sınıf ve morfizm miras ilişkilerini içeren bir kategorimiz var .
Bu biraz kurulum, ama bununla birlikte Diamond of Doom'umuza bir göz atalım:
C --> D
^ ^
| |
A --> B
Gölgeli görünümlü bir diyagram, ama olacak. Yani D
tüm devralır A
, B
ve C
. Dahası, OP'nin sorusunu ele almaya yaklaştıkça D
, herhangi bir üst sınıfından da miras kalır A
. Bir diyagram çizebiliriz
C --> D --> R
^ ^
| |
A --> B
^
|
Q
Şimdi, burada Diamond of Death ile ilişkili sorunlar, bazı özellik / yöntem adlarının ne zaman paylaşıldığı C
ve B
paylaşıldığı ve bazı şeylerin belirsizleştiği; ancak, paylaşılan bir davranışı içine A
taşırsak, belirsizlik ortadan kalkar.
Kategorik açıdan koyun, bizim istediğimiz A
, B
ve C
eğer böyle olması B
ve C
gelen devralır Q
sonra A
bir alt sınıf olarak tekrar yazılabilir Q
. Bu, pushout olarakA
adlandırılan bir şey yapar .
Geri çekmeD
adı verilen simetrik bir yapı da vardır . Bu temelde hem ve hem de mirasını oluşturabileceğiniz en genel yararlı sınıftır . Başka sınıf varsa Yani, çarpma devralan ve ardından bir sınıftır ait alt sınıf olarak tekrar yazılabilir .B
C
R
B
C
D
R
D
Pırlantanın ipuçlarının geri çekilmesinden ve itilmelerden emin olmanız, aksi takdirde ortaya çıkabilecek ad çakışması veya bakım sorunlarını genel olarak ele almamız için bize güzel bir yol sağlar.
Not Paercebal 'ın cevabı onun tembihleri hepimiz olası sınıfların tam kategorisinde Sınıf çalışması göz önüne alındığında yukarıda modelin ima olarak bu ilham verdi.
Argümanını, çoklu kalıtım ilişkilerinin ne kadar karmaşık ve güçlü olabileceğini gösteren bir şeye genelleştirmek istedim.
TL; DR Programınızdaki kalıtım ilişkilerini bir kategori oluşturmak olarak düşünün. Daha sonra, çok sayıda miras alınan sınıfları pushout ve simetrik olarak yaparak, geri çekilme olan ortak bir ebeveyn sınıfı yaparak Diamond of Doom problemlerinden kaçınabilirsiniz.
Eiffel kullanıyoruz. Mükemmel MI'ya sahibiz. Telaşa gerek yok. Sorun yok. Kolayca yönetilebilir. MI kullanmamanız gereken zamanlar vardır. Bununla birlikte, insanların fark ettiğinden daha yararlıdır çünkü: A) iyi yönetmeyen tehlikeli bir dilde -VEYA- B) yıllarca ve yıllarca MI'da nasıl çalıştıklarından memnunlar -VEYA- C) diğer nedenler ( listeden çok fazla eminim - yukarıdaki cevaplara bakın).
Bizim için, Eiffel'i kullanan MI, her şey kadar doğal ve araç kutusunda başka bir iyi araç. Açıkçası, başka hiç kimsenin Eiffel kullanmadığından endişe duyuyoruz. Telaşa gerek yok. Sahip olduğumuz şeyden mutluyuz ve sizi bir göz atmaya davet ediyoruz.
Bakarken: Void güvenliğini ve Null işaretçi kaydının kaldırılmasını özel olarak not edin. Hepimiz MI etrafında dans ederken, işaretçileriniz kayboluyor! :-)
Her programlama dili artıları ve eksileri ile nesne yönelimli programlama biraz farklı bir tedavi vardır. C ++ 'ın sürümü vurguyu kareli bir şekilde performansa yerleştirir ve eşlik eden dezavantajı geçersiz kod yazmanın rahatsız edici derecede kolay olmasıdır - ve bu çoklu kalıtım için geçerlidir. Sonuç olarak, programcıları bu özellikten uzaklaştırma eğilimi vardır.
Diğer insanlar çoklu mirasın neye yaramadığı sorusunu ele aldı. Ancak, bundan kaçınmanın nedeninin güvenli olmadığı için az ya da çok ima ettiği yönünde birkaç yorum gördük. Evet, hayır.
C ++ 'da sıklıkla geçerli olduğu gibi, temel bir yönergeyi izlerseniz, sürekli olarak "omzunuzun üzerinden bakmak" zorunda kalmadan güvenle kullanabilirsiniz. Anahtar fikir, "karma" olarak adlandırılan özel bir sınıf tanımını ayırt etmenizdir; class, tüm üye işlevleri sanal (veya salt sanal) ise bir karmadır. Daha sonra tek bir ana sınıftan ve istediğiniz sayıda "mix-in" den miras almanıza izin verilir - ancak mixins'i "sanal" anahtar kelimesiyle devralmanız gerekir. Örneğin
class CounterMixin {
int count;
public:
CounterMixin() : count( 0 ) {}
virtual ~CounterMixin() {}
virtual void increment() { count += 1; }
virtual int getCount() { return count; }
};
class Foo : public Bar, virtual public CounterMixin { ..... };
Benim önerim, bir sınıfı karma sınıf olarak kullanmayı planlıyorsanız, kodu okuyan herkesin neler olduğunu görmesini ve temel kılavuz kurallara göre oynadığınızı doğrulamasını kolaylaştırmak için bir adlandırma kuralı da benimsemenizdir. . Ayrıca, sanal temel sınıfların çalışma şekli nedeniyle, mix'lerinizin varsayılan kurucuları da varsa daha iyi çalıştığını göreceksiniz. Ve tüm yıkıcıları sanallaştırmayı da unutmayın.
Burada "mix-in" kelimesini kullanmanın parametrelenmiş şablon sınıfı ile aynı olmadığını unutmayın ( iyi bir açıklama için bu bağlantıya bakın ) ama terminolojinin adil bir kullanımı olduğunu düşünüyorum.
Şimdi, çoklu kalıtımın güvenli bir şekilde kullanılmasının tek yolu olduğu izlenimini vermek istemiyorum. Kontrol edilmesi oldukça kolay olan sadece bir yol.
Dikkatlice kullanmalısınız, Elmas Sorunu gibi, bazı şeylerin karmaşık olabileceği bazı durumlar var .
(kaynak: learncpp.com )
Printer
bile olmamalı PoweredDevice
. A Printer
, güç yönetimi için değil yazdırma içindir. Belirli bir yazıcının uygulanması bazı güç yönetimi yapmak zorunda kalabilir, ancak bu güç yönetimi komutları doğrudan yazıcının kullanıcılarına gösterilmemelidir. Bu hiyerarşiyi gerçek anlamda kullandığını düşünemiyorum.
Kalıtımın Kullanımları ve Kötüye Kullanımı.
Makale miras açıklamak için harika bir iş çıkarıyor ve tehlikeleri var.
Elmas deseninin ötesinde, çoklu kalıtım, nesne modelinin anlaşılmasını zorlaştırır ve bu da bakım maliyetlerini artırır.
Kompozisyonun anlaşılması, anlaşılması ve açıklanması kendiliğinden kolaydır. Kod yazmak için sıkıcı olabilir, ancak iyi bir IDE (Visual Studio ile çalışmamın üzerinden birkaç yıl geçti, ancak kesinlikle Java IDE'lerin hepsinde harika kompozisyon kısayol otomasyon araçları var) bu engelin üstesinden gelmelidir.
Ayrıca, bakım açısından, "elmas sorunu" da gerçek olmayan kalıtım örneklerinde ortaya çıkar. Örneğin, A ve B'ye sahipseniz ve C sınıfınız her ikisini de genişletiyorsa ve A, portakal suyu yapan bir 'makeJuice' yöntemine sahipse ve bunu bir kez kireçle portakal suyu yapmak için genişletirseniz: tasarımcı için ne olur? B 'elektrik akımı üreten bir' makeJuice 'yöntemi ekler mi? 'A' ve 'B' uyumlu "anne" olabilir şu anda , ama bu her zaman bu kadar olacak anlamına gelmez!
Genel olarak, kalıtımdan ve özellikle çoklu kalıtımdan kaçınma eğilimi sağlamdır. Tüm maximlar gibi, istisnalar vardır, ancak kodladığınız istisnalara işaret eden yanıp sönen yeşil bir neon işaretinin olduğundan emin olmanız gerekir (ve beyninizi eğitin, böylece böyle miras ağaçlarını her gördüğünüzde kendi yanıp sönen yeşil neonunuzda çizin işareti) ve ara sıra her şeyin anlamlı olduğunu kontrol ettiğinizden emin olun.
what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?
Uhhh, elbette derleme hatası alıyorsunuz (kullanım belirsiz ise).
Somut nesnelerin MI ile ilgili en önemli sorun, nadiren meşru bir şekilde "A VE B olması" gereken bir nesneye sahip olmanızdır, bu nedenle nadiren mantıksal zeminlerde doğru çözümdür. Çok daha sık olarak, "C, A veya B olarak hareket edebilir" e uygun bir C nesnesine sahipsiniz ve arayüz kalıtım ve kompozisyonuyla elde edebilirsiniz. Ancak çoklu arayüzlerin hata yapmaması hala MI, sadece bir altkümesidir.
Özellikle C ++ için, özelliğin temel zayıflığı, Çoklu Kalıtım'ın gerçek varlığı değildir, ancak izin verdiği bazı yapılar neredeyse her zaman yanlış biçimlendirilir. Örneğin, aynı nesnenin birden çok kopyasını devralmak gibi:
class B : public A, public A {};
, TANIMLAMA ile hatalı biçimlendirilmiş. İngilizceye çevrilmiş olan bu "B bir A ve A'dır". Yani, insan dilinde bile ciddi bir belirsizlik var. Şunu mu demek istediniz: "B has 2 As" ya da sadece "B is a A"? Böyle bir patolojik koda izin vermek ve daha kötüsü bir kullanım örneği yapmak, C ++ özelliğinin halef dillerinde tutulması için bir durum söz konusu olduğunda iyilik yapmamıştır.
Kompozisyonu kalıtım yerine kullanabilirsiniz.
Genel duygu, kompozisyonun daha iyi olduğu ve çok iyi tartışıldığıdır.
The general feeling is that composition is better, and it's very well discussed.
Bu kompozisyonun daha iyi olduğu anlamına gelmez.
ilgili sınıf başına 4/8 bayt gerekir. (Sınıf başına bir işaretçi).
Bu asla bir endişe kaynağı olmayabilir, ancak bir gün milyarlarca zaman süren bir mikro veri yapınız varsa.