Birden fazla kalıtımın nefret edilmesinin “gerçek” bir nedeni var mı?


123

Her zaman bir dilde çoklu miras alma fikrini sevdim. Çoğu zaman kasıtlı olarak affedilmiş olmasına rağmen ve sözde "değiştirme" arayüzlerdir. Arayüzler, çoklu kalıtımın aynı toprakları kapsamaz ve bu kısıtlama zaman zaman daha fazla kazan koduna yol açabilir.

Bunun için duyduğum tek temel sebep , temel sınıflardaki elmas problemi . Bunu kabullenemiyorum. Bana göre, "Şey, bunu mahvetmek mümkün , bu yüzden otomatik olarak kötü bir fikir." Her şeye rağmen bir programlama dilinde herhangi bir şeyi mahvedebilirsiniz, ve ben bir şey demek istiyorum. Bunu ciddiye alamam, en azından daha ayrıntılı bir açıklama yapmadan.

Sadece bu sorunun farkında olmak savaşın% 90'ı. Dahası, yıllar önce bir "zarf" algoritması ya da bunun gibi bir şey içeren genel amaçlı bir çalışma hakkında bir şeyler duyduğumu düşünüyorum.

Elmas sorunuyla ilgili olarak, düşünebildiğim tek gerçek sorun, üçüncü taraf bir kütüphane kullanmaya çalışıyorsanız ve bu kitaplıktaki görünüşte alakasız iki sınıfın ortak bir temel sınıfa sahip olduğunu göremiyorsanız, buna ek olarak Basit bir dil özelliği olan dokümantasyon, diyelim ki, sizin için bir tane derlemeden önce bir elmas oluşturma niyetinizi özellikle beyan etmenizi gerektirebilir. Böyle bir özelliğe sahip olarak, herhangi bir elmasın yaratılması ya kasıtlıdır, umursamazdır ya da kişi bu tuzaktan habersizdir.

Böylece herkes söyleniyor ... Çoğu insanın birden fazla mirastan nefret etmesinin gerçek bir nedeni var mı , yoksa hepsi iyiden daha çok zarara neden olan bir demet histeri mi? Burada görmediğim bir şey var mı? Teşekkür ederim.

Örnek

Araba WheeledVehicle'ı uzatır, KIASpectra Araba ve Elektronik'i uzatır, KIASpectra Radyo'yu içerir. KIASpectra neden Elektronik içermiyor?

  1. Bunun Çünkü olduğunu Elektronik. Kalıtım - kompozisyon, her zaman bir ilişki-bir ilişki-ilişki olmalıdır.

  2. Bunun Çünkü olduğunu Elektronik. Teller, devre kartları, anahtarlar, vb. Hepsi yukarı ve aşağı vardır.

  3. Bunun Çünkü olduğunu Elektronik. Bataryanız kışın tükenirse, tüm tekerlekleriniz bir anda kayboluyormuş gibi sorun yaşarsınız.

Neden arayüzleri kullanmıyorsunuz? Mesela # 3 atın. Bunu tekrar tekrar yazmak istemiyorum ve gerçekten de bunu yapmak için tuhaf bir proxy yardımcı sınıfı oluşturmak istemiyorum:

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(Uygulamanın iyi ya da kötü olup olmadığına girmiyoruz.) WheeledVehicle'daki herhangi bir şeyle ilgili olmayan, Elektronikle ilgili bu işlevlerden birkaçının nasıl olabileceğini hayal edebilirsiniz .

Bu örneğe karar verip vermeyeceğime emin değildim, çünkü orada yorumlamaya yer var. Uçak Uzatmalı Araç ve FlyingObject ve Kuş ve Hayvan ve FlyingObject'i uzatan kuş açısından veya daha saf bir örnek olarak düşünebilirsiniz.


24
aynı zamanda kompozisyona karşı kalıtımını da teşvik eder ... (başka bir şekilde olması gerektiğinde)
cırcır ucube

34
Bir özelliği kaldırmak için "bunu mahvedebilirsiniz" mükemmel bir sebep. Modern dil tasarımı bu noktada biraz dağılır, ancak dilleri genellikle "Kısıtlama Gücü" ve "Esneklik Gücü" olarak sınıflandırabilirsiniz. Hiçbiri "doğru" değil, sanırım ikisinin de güçlü noktaları var. MI, Kötülük için en çok kullanılan şeylerden biridir ve bu nedenle kısıtlayıcı diller onu ortadan kaldırır. Esnek olanları değil, çünkü "çoğu zaman değil", "tam anlamıyla her zaman" değildir. Bununla birlikte, karışımlar / özellikler genel durum için daha iyi bir çözüm olduğunu düşünüyorum.
Phoshi

8
Arayüzlerin ötesine geçen, birçok dilde çoklu kalıtımın daha güvenli alternatifleri vardır. Scala'ya göz atın Traits- isteğe bağlı uygulamalarla ara yüz gibi davranırlar, ancak elmas sorunu gibi sorunların ortaya çıkmasını önlemeye yardımcı olacak bazı kısıtlamaları vardır.
KChaloux

5
Daha önce bu kapalı soruya bakın: programmers.stackexchange.com/questions/122480/…
Doc Brown

19
KiaSpectradeğildir bir Electronic ; o vardır Elektronik ve bir olabilir ElectronicCar(uzanacak Car...)
Brian S

Yanıtlar:


68

Birçok durumda, insanlar kalıtımdan bir sınıfa özellik sağlamak için kullanırlar. Örneğin bir Pegasus düşünün. Birden fazla kalıtımla, Pegasus'un Atı ve Kuşu uzattığını söylemek isteyebilirsiniz, çünkü Kuşu kanatlı bir hayvan olarak sınıflandırdınız.

Bununla birlikte Kuşlar, Pegasi'nin sahip olmadığı başka özelliklere de sahiptir. Mesela kuşlar yumurtlar, Pegasi canlı doğar. Miras, paylaşılan özellikleri geçmenin tek yoluysa, yumurtlama özelliğini Pegasus'tan dışlamanın hiçbir yolu yoktur.

Bazı diller, özellikleri dil içinde açık bir yapı haline getirmeyi seçti. Diğerleri MI'yi dilden kaldırarak nazikçe sizi bu yönde yönlendirir. Her iki durumda da, "Bunu gerçekten yapmak için MI'ya ihtiyacım var dostum" diye düşündüğüm tek bir durum düşünemiyorum.

Ayrıca gerçekten mirasın ne olduğunu tartışalım. Bir sınıftan miras aldığınızda, o sınıfa bağımlılık duyarsınız, ancak aynı zamanda sınıfın hem dolaylı hem de açık olduğu desteklediği sözleşmeleri de desteklemelisiniz.

Klasik bir dikdörtgenden miras kalan kare örneğini alın. Dikdörtgen bir uzunluk ve genişlik özelliğini ve ayrıca bir getPerimeter ve getArea yöntemini gösterir. Kare uzunluğu ve genişliği geçersiz kılar, böylece biri ayarlandığında diğeri getPerimeter ile eşleşecek şekilde ayarlanır ve getArea aynı şekilde çalışır (alan için 2 * uzunluk + 2 * genişlik ve alan için uzunluk * genişlik).

Bir karenin bu uygulamasını bir dikdörtgen yerine koyarsanız kırılan tek bir test durumu vardır.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

Tek bir kalıtım zinciri ile işleri doğru yapmak yeterince zor. Karışıma bir tane daha eklediğinizde daha da kötüleşiyor.


MI'da Pegasus ve Dikdörtgen / Kare ilişkilerinden bahsettiğim tuzaklar, hem dersler için deneyimsiz bir tasarımın sonucudur. Temel olarak çoklu kalıtımdan kaçınmak, yeni başlayan geliştiricilerin kendilerini ayağından vurmaktan kaçınmasına yardımcı olan bir yoldur. Tüm tasarım ilkeleri gibi, disipline ve onlara dayalı bir eğitime sahip olmak, zaman içinde onlardan ayrılmanın ne zaman uygun olduğunu keşfetmenizi sağlar. Dreyfus Beceri Edinme Modeline bakın, Uzman seviyesinde, gerçek bilginiz maksimuma / ilkelere güveniyor. Bir kural uygulanmadığında "hissedebilirsiniz".

MI'nin kaşlarını çattığına dair "gerçek dünya" örneği ile biraz aldattığımı kabul ediyorum.

UI çerçevesine bakalım. Özellikle, ilk başta fırçanın, sadece diğerlerinin bir birleşimi gibi görünebileceği birkaç widget'a bakalım. Bir ComboBox gibi. Bir ComboBox, bir DropDownList'i destekleyen bir TextBox'tır. Yani bir değer yazabilir veya önceden tanımlanmış bir değerler listesinden seçim yapabilirim. Naif bir yaklaşım ComboBox'u TextBox ve DropDownList'ten devralmak olacaktır.

Ancak Metin Kutunuz, değerini kullanıcının yazdıklarından alır. DDL, kullanıcının seçtiği değerden değerini alırken. Kim emsal alır? DDL, orijinal değerler listesinde bulunmayan herhangi bir girişi doğrulamak ve reddetmek için tasarlanmış olabilir. Bu mantığı geçersiz kılıyor muyuz? Bu, mirasçıların geçersiz kılmaları için iç mantığı açığa çıkarmamız gerektiği anlamına gelir. Daha da kötüsü, yalnızca bir alt sınıfı desteklemek için orada bulunan temel sınıfa mantık ekleyin ( Bağımlılık İnversiyonu İlkesini ihlal eden ).

MI'dan kaçınmak, bu tuzağı tamamen kaldırmanıza yardımcı olur. Ve UI widget'larınızın ortak, yeniden kullanılabilir özelliklerini çıkarmanıza ve böylece gerektiğinde uygulanabilmelerine yol açabilir. Bunun mükemmel bir örneği, WPF'deki bir çerçeve elemanının, başka bir çerçeve elemanının, ana çerçeve elemanından miras almadan kullanabileceği bir özellik sağlamasına izin veren WPF Ekli Mülkiyet'tir .

Örneğin bir Izgara, WPF'deki bir yerleşim panosudur ve bir alt öğenin ızgara düzenlemesinde nereye yerleştirilmesi gerektiğini belirten Sütun ve Satır ekli özelliklere sahiptir. Eklenmiş özellikler olmadan, bir Izgara içinde bir Düğme düzenlemek istersem, Düğme, Izgaradan türetmek zorundadır, böylece Sütun ve Satır özelliklerine erişebilir.

Geliştiriciler bu kavramı daha da ileri götürdü ve ekli özellikleri davranışsallaştırmanın bir yolu olarak kullandı (örneğin, burada WPF'nin bir DataGrid içermesinden önce yazılan ekli özellikleri kullanarak sıralanabilir bir GridView yapma konusundaki görevim ). Yaklaşım, Ekli Davranışlar adı verilen bir XAML Tasarım Deseni olarak kabul edilmiştir .

Umarım bu, Çoklu Kalıtımın neden tipik olarak kaşlarını çattığına ilişkin daha fazla bilgi sağlamıştır.


14
"Adamım bunu düzgün yapmak için MI'ya ihtiyacım var" - bir örneklem için cevabımı gör. Kısaca, bir ilişkiyi sağlayan ortogonal kavramlar MI için anlamlı olur. Çok nadir görülürler ama varlar.
MrFox

13
Dostum, o kare dikdörtgen örneğinden nefret ediyorum. Değişkenlik gerektirmeyen daha aydınlatıcı örnekler vardır.
asmeurer,

9
"Gerçek bir örnek vermenin" cevabının kurgusal bir yaratığı tartışmak olduğunu seviyorum. Mükemmel ironi +1
jjathman

3
Yani, çoklu mirasın kötü olduğunu söylüyorsunuz, çünkü miras kötüdür (örneklerin hepsi tek bir arabirim mirası ile ilgilidir). Bu nedenle, yalnızca bu cevaba göre, bir dil mirastan ve arayüzlerden kurtulmalı ya da çoklu mirasa sahip olmalıdır.
ctrl-alt-delor,

12
Bu örneklerin gerçekten işe yaradığını sanmıyorum ... kimse pegasus'un bir kuş ve at olduğunu söylemez ... diyorsunuz ki bu bir KanatlıHayvan ve TırnaklıHayvan. Kare ve dikdörtgen örnek biraz daha mantıklı çünkü kendini tanımladığınız tanımı temel alarak düşündüğünüzden farklı davranan bir nesne ile buluyorsunuz. Bununla birlikte, bu durumun programcıların bunu yakalayamama hatası olacağını düşünüyorum (eğer bu bir sorun olarak ortaya çıkarsa).
richard

60

Burada görmediğim bir şey var mı?

Birden fazla kalıtsallığa izin vermek, aşırı işlevler ve sanal gönderim ile ilgili kuralları ve ayrıca nesne düzenleri etrafındaki dilin uygulanmasına ilişkin kuralları çok daha zorlaştırır. Bu, dil tasarımcılarını / uygulayıcılarını biraz etkiler ve bir dilin yapılmasını, sabitlenmesini ve benimsenmesini sağlamak için zaten yüksek olan çıtayı yükseltir.

Gördüğüm (ve zaman zaman yaptığım) başka bir ortak argüman, iki + taban sınıfına sahip olarak, nesnenizin neredeyse her zaman Tek Sorumluluk İlkesini ihlal ettiğidir. İki + temel sınıf, kendi sorumlulukları olan (ihlale neden olan) kendi kendine yeten hoş sınıflardır veya tek bir yapışkan sorumluluk oluşturmak için birbirleriyle çalışan kısmi / soyut türlerdir.

Bu durumda, 3 senaryo var:

  1. B hakkında hiçbir şey bilmiyor - Harika, şanslıydın çünkü sınıfları birleştirebilirsin.
  2. B hakkında bir şey biliyor - Neden A, B'den yeni miras kalmadı?
  3. A ve B birbirlerini tanıyor - Neden sadece bir sınıf yapmadın? Bunları bu kadar eşleşmiş fakat kısmen değiştirilebilmekten ne faydası var?

Şahsen, çoklu kalıtımın kötü bir rap yapabileceğini ve iyi yapılmış bir özellik stili kompozisyon sisteminin gerçekten güçlü / faydalı olacağını düşünüyorum ... ama bunun kötü bir şekilde uygulanmasının birçok nedeni var ve bunun birçok nedeni var. C ++ gibi bir dilde iyi bir fikir değil.

[edit] örneğinizle ilgili, bu çok saçma. Bir Kia sahiptir elektronik. Bu sahiptir bir motor. Aynı şekilde, elektronik aksamın sadece araba aküsü olduğu bir güç kaynağı var . Miras, tek başına çoklu mirasın orada yeri yoktur.


8
Bir başka ilginç soru da, temel sınıfların + temel sınıflarının nasıl başlatıldığı. Kapsülleme> Kalıtım.
jozefg

"özellik tarzı kompozisyonunun aferin sistem gerçekten / güçlü faydalı olacaktır ..." - Bunlar bilinen Mixins ve bunlar şunlardır yararlı / güçlü. Karışımlar MI kullanılarak uygulanabilir, ancak karışımlar için Çoklu Kalıtım gerekmez. Bazı diller MI olmadan doğal olarak karışımları destekler.
BlueRaja - Danny Pflughoeft 14:13

Özellikle Java için, zaten çok sayıda arabirime ve INVOKEINTERFACE'e sahibiz, bu nedenle MI'nın herhangi bir belirgin performans etkisi olmaz.
Daniel Lubarov

Tetastyn, örneğin kötü olduğuna ve sorusuna hizmet etmediğine katılıyorum. Kalıtım sıfatlar (elektronik) için değil, isimler (araç) için de geçerlidir.
richard

29

İzin vermemesinin tek nedeni, insanların kendilerini ayaklarından vurmalarını kolaylaştırmasıdır.

Bu tür bir tartışmada genellikle izlenen şey, aletlere sahip olmanın esnekliğinin ayağınızı vurmamanın güvenliğinden daha önemli olup olmadığı argümanlarıdır. Bu argümana kesin olarak doğru bir cevap yoktur, çünkü programlamadaki diğer birçok şey gibi cevap da bağlama bağlıdır.

Geliştiricileriniz MI konusunda rahatlarsa ve MI yaptığınız bağlamda mantıklıysa, onu desteklemeyen bir dilde çok özleyeceksiniz. Aynı zamanda, takım onunla rahat hissetmiyorsa veya gerçek bir ihtiyaç yoksa ve insanlar onu 'sadece yapabildikleri için' kullanırlar, o zaman bu tersine üretkendir.

Fakat hayır, çoklu kalıtımın kötü bir fikir olduğunu kanıtlayan tamamen inandırıcı ve kesinlikle doğru bir argüman yoktur.

DÜZENLE

Bu sorunun cevapları oybirliği ile görünüyor. Şeytanın savunucusu olmak uğruna, bunu yapmamanın saldırılara yol açacağı çok sayıda kalıtımın güzel bir örneğini sunacağım.

Bir sermaye piyasası uygulaması tasarladığınızı varsayalım. Menkul kıymetler için bir veri modeline ihtiyacınız var. Bazı menkul kıymetler özkaynak ürünleridir (hisse senetleri, gayrimenkul yatırım fonları, vb.) Diğerleri borçtur (tahviller, şirket tahvilleri), diğerleri türevlerdir (opsiyonlar, vadeli işlemler). Öyleyse MI'dan kaçıyorsanız, çok açık ve basit bir miras ağacı oluşturacaksınız. Bir Hisse Senedi miras alacak, Bond Borç Miras alacak. Şimdilik harika, peki ya türevler? Equity benzeri ürünlere veya Borç benzeri ürünlere dayanabilirler mi? Tamam, sanırım miras ağacımızı daha çok açacağız. Akılda tutulması gereken bazı türevler, özkaynak ürünlerinden, borç ürünlerinden veya hiçbirinden değildir. Yani miras ağacımız karmaşıklaşıyor. Ardından iş analisti geliyor ve şimdi endeksli menkul kıymetleri desteklediğimizi söylüyor (endeks opsiyonları, endeks gelecekteki opsiyonları). Ve bu şeyler Eşitlik, Borç veya Türevten kaynaklanabilir. Bu dağınık oluyor! Gelecekteki endeks opsiyonum, Hisse Senedi - Hisse Senedi -> Seçenek-> Endeks olarak mı ortaya çıkar? Neden Hisse Senedi-> Hisse Senedi-> Endeks-> Opsiyon? Ya bir gün her ikisini de kodumda bulursam (Bu oldu; gerçek hikaye)?

Buradaki sorun, bu temel türlerin, doğal olarak birini diğerinden türetmeyen herhangi bir permütasyonda karıştırılabilmesidir. Nesneler bir ilişki ile tanımlanır , bu yüzden kompozisyon hiçbir anlamı yoktur. Çoklu kalıtım (veya benzer karışım kavramı) buradaki mantıksal temsildir.

Bu problemin asıl çözümü, veri modelinizi oluşturmak için çoklu miras kullanılarak tanımlanan Özkaynak, Borç, Türev, Endeks tiplerine sahip olmaktır. Bu, yeniden kullanımı kodlamak için kolayca hem mantıklı hem de ödünç veren nesneler yaratacaktır.


27
Finans alanında çalıştım. Finansal yazılımda işlevsel programlamanın OO yerine tercih edilmesinin bir nedeni var. Ve sen onu işaret ettin. MI bu sorunu daha da kötüleştirmez. İnan bana, kaç kez duyduğumu söyleyemem "Aynen böyle ... hariç", finans müşterileriyle uğraşırken Varlığımın belası olur.
Michael Brown

8
Bu, bana olan arabirimlerle çok çözülebilir görünüyor ... aslında, arabirimler için her şeyden daha uygun görünüyor . Equityve Debther ikisi de uygular ISecurity. Derivativebir ISecurityözelliği var. ISecurityUygunsa kendisi de olabilir (finansı bilmiyorum). IndexedSecuritiesyine dayanmasına izin verilen türlere uygulanan bir arabirim özelliği içerir. Eğer hepsi buysa ISecurity, hepsinin de ISecuritymülkiyeti var ve keyfi olarak yuvalanabilirler ...
Bobson

11
@Bobson'da sorun, her uygulayıcı için arayüzü yeniden uygulamanız gerekmesidir ve çoğu durumda aynıdır. Delegasyonu / kompozisyonu kullanabilirsiniz, ancak daha sonra özel üyelere erişiminizi kaybedersiniz. Java delegelere / lambdalara sahip olmadığı için ... bunu yapmanın iyi bir yolu yok. Muhtemelen Aspect4J gibi bir Aspect aracıyla
Michael Brown

10
@ Bobson tam olarak Mike Brown'ın söylediklerini. Evet, arayüzleri ile bir çözüm tasarlayabilirsiniz, ancak tıknaz olacaktır. Arayüz kullanma sezginiz çok doğru olsa da, karışımlar / çoklu kalıtım için gizli bir istek :).
MrFox

11
Bu, OO programcılarının kalıtımsallık ve düşünme yönünden düşünmeyi tercih ettiği bir tuhaflığa değinmekte, delegasyonu tipik olarak daha yalın, daha sürdürülebilir ve genişletilebilir bir çözüm sağlayan geçerli bir OO yaklaşımı olarak kabul etmemektedir. Finans ve sağlık alanında çalıştım, yönetilemez bir karışıklık MI'nin yarattığını gördüm, özellikle vergi ve sağlık yasaları yıldan yıla değişiyor ve bir nesne tanımının LAST yılı, bu yıl için geçersiz ve değiştirilebilir olmalıdır). Kompozisyon, test edilmesi daha kolay olan ve daha az zaman harcayan daha ucuz kodlar verir.
Shaun Wilson,

11

Buradaki diğer cevaplar çoğunlukla teoriye giriyor gibi görünüyor. İşte burada, basitleştirilmiş, oldukça uzun bir süre boyunca yeniden yapılanma gerektiren, baştan aşağı çarptığım somut bir Python örneği:

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Barzeta()genellikle oldukça iyi bir varsayım olan kendi uygulamasına sahip olduğu varsayılarak yazılmıştır . Bir alt sınıf, doğru olanı yapması için uygun şekilde geçersiz kılmalıdır. Ne yazık ki, isimler sadece tesadüfen aynıydı - çok farklı şeyler yaptılar, ama Barşimdi de Foouygulamalarını çağırıyorlardı :

foozeta
---
barstuff
foozeta

Atılan hiçbir hata olmadığında oldukça sinir bozucu olur, uygulama çok az yanlış davranmaya başlar ve buna neden olan kod değişikliği (yaratma Bar.zeta) sorunun nerede olduğu gibi görünmez.


Diamond problemi çağrılar yoluyla nasıl önlenir super()?
Panzercrisis

@Panzercrisis Üzgünüz, o kısmı boşver. Python elmas biçimli miras nedeniyle ayrı bir sorunu vardı - "elmas sorunu" genellikle atıfta hangi sorun yanlış hatırlıyor super()etrafında var
Izkata

5
C ++ 'nin MI'sinin daha iyi tasarlanmış olmasına sevindim. Yukarıdaki hata basitçe mümkün değildir. Kod bile derlenmeden önce elle manuel olarak ayırmanız gerekir.
Thomas Eding,

2
Devralmayla sırasını değiştirme Bangiçin Bar, Foode sorunu çözmek istiyorum - ama nokta, 1 iyi bir tanesidir.
Sean Vieira

1
You @ThomasEding elle hala sorun aşılabilir: Bar.zeta(self).
Tyler Crompton

9

MI ile doğru dilde herhangi bir gerçek sorun olmadığını savunuyorum . Anahtar, elmas yapılara izin vermektir, ancak alt tiplerin, bazı kurallara dayanan uygulamalardan birini seçmek yerine derleyici yerine kendi geçersiz kılmalarını sağlamalarını gerektirir.

Bunu üzerinde çalıştığım bir dil olan Guava'da yapıyorum . Guava'nın bir özelliği de, belirli bir süper tipin bir metot uygulamasına başlayabilmemizdir. Bu nedenle, hangi süper tip uygulamanın özel bir sözdizimi olmadan "miras" olması gerektiğini belirtmek kolaydır:

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

Eğer OrderedSetkendine ait toStringolmasaydık, bir derleme hatası alırdık. Sürpriz yok.

MI koleksiyonları ile özellikle yararlı buluyorum. Örneğin, diziler, deklar vb. İçin bildirimde RandomlyEnumerableSequencebulunmaktan kaçınmak getEnumeratoriçin bir tür kullanmayı seviyorum :

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

MI olmasaydı, RandomAccessEnumeratorbirkaç koleksiyon kullanmak için yazabilirdik, ancak kısa bir getEnumeratoryöntem yazmak zorunda kalmaya devam ediyoruz .

Benzer şekilde, MI standart uygulamalarını miras için yararlıdır equals, hashCodeve toStringkoleksiyonları için.


1
@AndyzSmith, amacınızı anlıyorum, ancak tüm miras çatışmaları gerçek anlamsal problemler değil. Örneğimi ele alalım - türlerden hiçbiri toStringdavranışı hakkında hiçbir şey vaat etmedi, bu nedenle davranışını geçersiz kılmak yerine koyma ilkesini ihlal etmiyor. Bazen aynı davranışa sahip, ancak farklı algoritmalara, özellikle koleksiyonlara sahip olan yöntemler arasında "çatışmalar" elde edersiniz.
Daniel Lubarov

1
@Asmageddon kabul etti, örneklerim sadece işlevselliği içeriyor. Mixins'in avantajları olarak ne görüyorsunuz? En azından Scala'da, özellikler aslında MI'ye izin veren soyut sınıflardır - bunlar hala kimlik için kullanılabilir ve yine de elmas problemini ortaya çıkarmaktadır. Karışımların daha ilginç farklılıklara sahip olduğu başka diller var mı?
Daniel Lubarov

1
Teknik olarak soyut sınıflar arayüzler olarak kullanılabilir, bana göre, yapının kendisinin bir "kullanım ipucu" olması gerçeği, yani kodu gördüğümde ne olacağını bildiğim anlamına gelir. Bununla birlikte, şu anda doğal OOP modeline sahip olmayan Lua'da kodlama yapıyorum - mevcut sınıf sistemleri genellikle karışım olarak tabloları dahil etmeye izin veriyor ve kodladığım sistemde karışım olarak sınıfları kullanıyor. Tek fark, kimlik için yalnızca (tek) miras, işlevsellik için karışımlar (kimlik bilgisi olmadan) ve daha fazla kontrol için arayüzler kullanabilmenizdir. Belki bir şekilde daha iyi değil, ama bana mantıklı geliyor.
Llamageddon

2
Neden Ordered extends Set and Sequencea denir Diamond? Bu sadece bir Katıl. hatKöşe yoksundur . Neden ona elmas diyorsun? Sordum burada ama bir tabu soru gibi görünüyor. Bu triangualar yapıya Join'ten ziyade Diamond demeye ihtiyacınız olduğunu nasıl bildiniz?
Val,

1
@RecognizeEvilasWaste, o dilin Topilan edeceği kesin bir türe sahip toStringolduğundan, aslında bir elmas yapısı vardı. Ancak bir noktaya sahip olduğunuzu düşünüyorum - elmas bir yapıya sahip olmayan bir "birleşme" benzer bir sorun yaratır ve çoğu dil her iki durumda da aynı şekilde işler.
Daniel Lubarov

7

Kalıtım, çoklu veya başka türlü, o kadar önemli değildir. Farklı türde iki nesne ikame edilebilirse, mirasa bağlı olmasalar bile, önemli olan budur.

Bağlantılı bir liste ve karakter dizgisinin çok az ortak yanları vardır ve kalıtımla ilişkilendirilmeleri gerekmez, ancak lengthher ikisindeki öğelerin sayısını almak için bir işlev kullanabilirsem kullanışlıdır .

Kalıtım, kodun tekrar tekrar uygulanmasından kaçınmak için bir püf noktasıdır. Eğer kalıtım sizi kurtarırsa ve birden fazla kalıtım, tek mirasa kıyasla daha fazla çalışmanızı sağlar, o zaman ihtiyaç duyulan tüm gerekçe budur.

Bazı dillerin çoklu mirası çok iyi uygulayamadığından ve bu dillerin uygulayıcıları için, çoklu mirasın anlamı budur. Bir C ++ programcısına çoklu mirastan bahsedin ve akla gelen şey, bir sınıfın iki farklı miras yolu üzerinden bir tabanın iki kopyasıyla sona erdiği, ve virtualbir taban sınıfında kullanılıp kullanılmayacağı ve yıkıcıların nasıl adlandırılacağı konusundaki kafa karışıklığı ile ilgili meseleler. , ve bunun gibi.

Birçok dilde, sınıf mirası, sembollerin mirası ile sınırlıdır. B sınıfından bir D sınıfı aldığınızda, yalnızca bir tür ilişkisi oluşturmazsınız, ancak bu sınıflar aynı zamanda sözcüksel ad alanları olarak da işlev görürler, ayrıca B ad alanından D ad alanına sembollerin içe aktarılmasıyla da uğraşırsınız. B ve D tipleri ile neler olup bittiğinin anlambilimi. Bu nedenle çoklu kalıtım, sembol çatışması sorunlarına yol açar. Her ikisinin de bir metodu olan card_deckve graphicher ikisinin de bir drawmetodu var drawmı? Bu problemi olmayan bir nesne sistemi, Common Lisp'teki sistemdir. Belki de tesadüfen değil, çoklu miras Lisp programlarında kullanılıyor.

Kötü uygulanmış, uygunsuz bir şey (çoklu kalıtım gibi) nefret edilmelidir .


2
Length () list (string) ve string container yöntemiyle olan örneğiniz kötüdür, çünkü bu ikisi tamamen farklı şeyler yapıyor. Kalıtımın amacı, kod tekrarını azaltmak değildir. Kod tekrarını azaltmak istiyorsanız, ortak parçaları yeniden sınıflandırabilirsiniz.
BЈовић

1
@ BЈовић İki "tamamen" farklı şeyler yapmıyor.
Kaz

1
Dize ve listeyi bölmek , bir çok tekrarı görebilir. Bu yaygın yöntemleri uygulamak için kalıtım kullanmak yanlış olacaktır.
BЈовић

1
@ BЈовић Cevapta bir dize ve listenin mirasa bağlı olması gerektiği söylenemez; tam tersi. Bir işlemdeki bir listeyi veya dizgiyi değiştirebilme davranışı, lengthkalıtım kavramından bağımsız olarak faydalıdır (bu durumda yardımcı olmaz: Uygulamayı paylaşmayı deneyerek hiçbir şey elde edemeyiz) lengthişlevin iki yöntemi ). Yine de, bazı soyut kalıtımsallıklar olabilir: örneğin hem liste hem de karakter dizisi tiptedir (ancak herhangi bir uygulama sağlamaz).
Kaz

2
Ayrıca, devralma , parçaları ortak bir sınıfa yeniden yansıtmanın bir yoludur: yani temel sınıf.
Kaz

1

Söyleyebileceğim kadarıyla, sorunun bir kısmı (tasarımınızı anlamak biraz zorlaştırmanın yanı sıra (bununla birlikte kodlamak daha kolay)), derleyicinin sınıf verileriniz için yeterli alan kazandıracağını ve bunun çok büyük bir aşağıdaki durumda hafıza israfı:

(Örneğim en iyi olmayabilir, ancak aynı amaç için birden fazla bellek alanı hakkında özü öğrenmeye çalışın, aklıma ilk gelen buydu: P)

Sınıf köpeğinin caninus ve evcil hayvandan uzandığı bir DDD'yi düşünün, bir caninus, dietKg adı altında yemesi gereken (tamsayı) yiyecek miktarını belirten bir değişkene sahiptir, ancak bir evcil hayvan da aynı amaç için başka bir değişkene sahiptir. name (başka bir değişken adı ayarlamadıysanız, sonradan kaçmak istediğiniz, bout değişkenlerinin bütünlüğünü idare etmek ve saklamak istediğiniz ilk problem olan ekstra kodu kodlarsınız), sonra tam olarak iki bellek alanına sahip olursunuz. Aynı amaç, bundan kaçınmak için derleyicinizi aynı ad alanı altında tanıyacak şekilde değiştirmeniz ve yalnızca bu veriye tek bir bellek alanı atamanız gerekir; bu da ne yazık ki derleme zamanını belirleyemez.

Elbette, böyle bir değişkenin zaten başka bir yerde tanımlanmış bir boşluğu olabileceğini belirtmek için bir dil tasarımı tasarlayabilirsiniz, ancak programın sonunda bu değişkenin referans aldığı bellek alanının (ve yine ekstra kodun) nerede olduğunu belirtmesi gerekir.

Bana bu düşünceyi uygulayan insanlara güvenin, ama bu konuda gerçekten çok zor, ama sorduğuma sevindim, nazik bakış açınız paradigmaları değiştiren; çok aşamalı bir derleyici uygulanmalı ve çok karmaşık bir derlemelidir), "henüz" (çoklu kalıtım) yapabilecek kendi derleyiciniz için bir proje başlattıysanız, henüz var olmadığını söylüyorum, lütfen bana bildirin. Takımınıza katılmaktan mutluluk duyacağım.


1
Bir derleyici hangi noktada farklı ad sınıflarından gelen aynı isimdeki değişkenlerin bir araya getirilmesinin güvenli olduğunu varsayabilir? Caninus.diet ve pet.diet'in aslında aynı amaca hizmet etmesinin garantisi nedir? Aynı isimdeki fonksiyon / yöntemlerle ne yapardınız?
Mr.Mindor

hahaha Size tamamen katılıyorum @ Sayın Bayor, tam olarak cevabımda söylediğim şey, bu yaklaşıma yaklaşmak için aklıma gelen tek yol prototip odaklı programlama, ama imkansız demiyorum. ve bu tam olarak derlenme süresi boyunca birçok varsayım yapılması gerektiğini ve bazı ekstra "veri" / özelliklerin tehdici tarafından yazılması gerektiğini söylememin sebebi (yine de asıl sorun olan kodlamadır)
Ordiel

-1

Uzunca bir süredir, bazı programlama görevlerinin diğerlerinden ne kadar farklı olduğu - ve eğer kullanılan dillerin ve kalıpların problem alanına göre uyarlanmasının ne kadar yardımcı olduğunu aklıma getirmedim.

Yalnız çalışıyorsanız ya da çoğunlukla yazdığınız kod üzerine izole ettiğinizde, Hindistan'da bir yıl boyunca üzerinde herhangi bir geçiş yardımı olmadan size yardım eden 40 kişiden oluşan bir kod temeli devralmaktan tamamen farklı bir problem alanıdır.

Hayalinizdeki şirket tarafından işe alındığınızı ve daha sonra böyle bir kod temelini aldığınızı hayal edin. Dahası, danışmanların kalıtım ve çoklu miras hakkında bilgi edindiğini (ve dolayısıyla etkilediğini) hayal edin ... Üzerinde çalıştığınız şeyi hayal edebilir misiniz?

Kodu devraldığınızda en önemli özellik, anlaşılabilir olması ve ayrı ayrı çalışılabilmesi için parçalar izole edilmesidir. İlk önce çoklu miras gibi kod yapılarını yazdığınızdan emin olun, sizi küçük bir kopyadan kurtarabilir ve o zamanki mantıksal ruh halinize uyacak gibi görünebilir, ancak bir sonraki adamın çözmesi gereken daha fazla şey var.

Kodunuzdaki her ara bağlantı aynı zamanda çoklu mirasla iki defa parçaları bağımsız olarak anlamanızı ve değiştirmenizi zorlaştırır.

Bir ekibin parçası olarak çalışırken, size gereksiz hiçbir mantık vermeyen en basit kodu hedeflemek istersiniz. (DRY gerçekten ne anlama geliyor, kodunuzu asla değiştirmek zorunda kalmayacaksınız. Bir problemi çözmek için yerler!)

DRY kodunu elde etmenin Birden Çok Kalıtımdan daha basit yolları vardır; bu nedenle bir dilde olması sizi yalnızca sizin gibi aynı düzeyde anlamayan başkaları tarafından eklenen sorunlara açabilir. Yalnızca, diliniz size DRY kodunu saklamanız için basit / daha az karmaşık bir yol sunamadığında bile caziptir.


Dahası, danışmanların öğrendiklerini hayal edin - danışmanların her şeyi kabul edilemez şekilde sarsılmış bir karmaşa yapmak için arabirim tabanlı DI ve IoC ile delirdikleri MI olmayan diller (ör. Net) gördüm. MI bunu daha da kötüleştirmiyor, muhtemelen daha iyisini yapıyor.
gbjbaanb

@gbjbaanb Haklısınız, herhangi bir özelliği karıştırmaktan yanmamış biri için kolaydır - aslında en iyi şekilde nasıl kullanılacağını görmek için karıştırdığınız bir özelliği öğrenmek bir zorunluluktur. Biraz meraklıyım çünkü MI'ye sahip olmadığımı daha fazla DI / IoC zorladığımı söylüyor gibi görünüyorsunuz - tüm bu berbat arayüzlerle / DI ile aldığınız danışman kodunun olacağını düşünmüyorum. dahası, çılgın çoktan çoğa miras ağacına sahip olmaktan daha iyi, ancak MI ile olan deneyimim olabilir. DI / IoC'yi MI ile değiştirmek üzerine okuyabileceğim bir sayfanız var mı?
Bill K,

-3

Çoklu kalıtımın en büyük argümanı, bazı yararlı yeteneklerin sağlanabileceği ve bazı yararlı aksiyomların, onu (*) ciddi şekilde kısıtlayan bir çerçevede tutabileceği, ancak bu kısıtlamalar olmadan sağlanamayacağı ve / veya tutamayacağıdır. Onların arasında:

  • Birden fazla ayrı derlenmiş modüllere sahip olma yeteneği, diğer modüllerin sınıflarından miras alan sınıfları içerir ve bu temel sınıftan miras alan her modülü yeniden derlemek zorunda kalmadan bir taban sınıfı içeren bir modülü yeniden derler.

  • Bir türün, yeniden türetmek zorunda kalmadan türetilmiş bir tür olmadan bir üst sınıftan üye bir uygulamayı devralması

  • Herhangi bir nesne örneğinin doğrudan kendisine veya temel türlerine veya herhangi bir birleşim veya sıraya göre yukarı veya aşağı doğru tahrip olabileceği aksiyomu ve bu tür yukarı veya aşağı doğru herhangi bir kombinasyon veya dizide her zaman kimlik koruyucu

  • Türetilmiş bir sınıf, bir temel sınıf üyesine geçersiz kılsa ve zincirlenirse, temel üyeye doğrudan zincir kodu tarafından çağrılacağı ve başka hiçbir yerde kullanılmayacağı aksiyomu.

(*) Tipik olarak, birden fazla kalıtımı destekleyen türlerin sınıflar yerine "arayüzler" olarak bildirilmesi ve arayüzlerin normal sınıfların yapabileceği her şeyi yapmasına izin vermemesi istenerek istenir.

Eğer bir kişi genelleştirilmiş çoklu mirasa izin vermek isterse, başka bir şeyin yoluna girmesi gerekir. Eğer hem X hem de Y, B'den miras alırlarsa, her ikisi de aynı M üyesini ve zinciri baz uygulamasına geçersiz kılar ve D, X ve Y'den miras alır ancak M'yi geçersiz kılmazsa, o zaman D tipi bir örnek verildiğinde, ne yapmalı (( B) q). Böyle bir alçıya izin vermemek, herhangi bir nesnenin herhangi bir taban tipine göre yükseltilebileceğini söyleyen aksiyomu ihlal eder, ancak alçı ve üye çağrılmasının olası herhangi bir davranışı, yöntem zincirlemesine ilişkin aksiyomu ihlal eder. Bir kişi, sınıfların yalnızca derlendikleri bir temel sınıfın belirli bir sürümüyle birlikte yüklenmesini gerektirebilir, ancak bu genellikle gariptir. Çalışma zamanının, herhangi bir ataya birden fazla yoldan erişilebilecek herhangi bir türü yüklemeyi reddetmesi uygulanabilir ancak çoklu kalıtımın faydasını büyük ölçüde sınırlar. Paylaşılan kalıtım yollarına izin vermek, yalnızca herhangi bir çakışma olmadığı zaman, eski bir X sürümünün eski veya yeni bir Y ile uyumlu olacağı ve eski bir Y'nin eski veya yeni bir X ile uyumlu olacağı durumlar yaratacaktır, ancak yeni X ve yeni Y, uyumlu olması, kendi içinde hiçbir şeyin kırılması gereken bir şey olması beklenmeyen bir şey yapmamış olsa bile.

Bazı diller ve çerçeveler, MI'dan elde edilenin, izin vermek için verilmesi gerekenden daha önemli olduğu teorisi üzerine, çoklu mirasa izin vermektedir. Bununla birlikte, MI'nin maliyetleri önemlidir ve çoğu durumda, ara yüzler, MI'nin faydalarının% 90'ını maliyetin küçük bir bölümünde sağlar.


Aşağı oy kullananlar yorum yapmak ister mi? Cevabımın, diğer devralmaların reddedilmesiyle elde edilebilecek anlamsal avantajlarla ilgili olarak diğer dillerde bulunmayan bilgiler sağladığına inanıyorum. Çoklu mirasa izin vermenin başka faydalı şeylerden vazgeçmeyi gerektirmesi gerçeği, bu diğer şeylere MI'ye karşı olan çoklu mirastan daha fazla değer veren herkes için iyi bir neden olacaktır.
supercat,

2
MI ile ilgili problemler, tek bir kalıtımla kullanacağınız teknikleri kullanarak kolayca çözülür veya tamamen önlenir. Bahsettiğiniz yeteneklerin / aksiyomların hiçbiri ikincisi dışında MI tarafından engellenmiyor ... ve aynı yöntem için farklı davranışlar sağlayan iki sınıftan miras alıyorsanız, yine de bu belirsizliği çözmek sizin üzerinizde. Ancak, bunu yapmak zorunda kaldığınızı hissederseniz, muhtemelen zaten mirası yanlış kullanmaktasınız.
cHao

@Hao: Eğer türetilmiş sınıfların ana metodları geçersiz kılması ve bunlara zincirleme yapmaması gerekiyorsa (arayüzlerle aynı kural) bir problemi önleyebilir, fakat bu oldukça ağır bir sınırlamadır. Biri, FirstDerivedFoogeçersiz kılınması geçersiz kılan Barkorumalı olmayan sanal zincirlere zincirleme yapmaktan başka bir şey yapmadığında FirstDerivedFoo_Barve SecondDerivedFoogeçersiz kılma zincirlerini sanal olmayan korumalı zincirlere kullanıyorsa SecondDerivedBarve temel yönteme erişmek isteyen kod sanal olmayan korumalı yöntemler kullanıyorsa, bu uygulanabilir bir yaklaşım olabilir ...
supercat

... (sözdiziminden kaçınıyor base.VirtualMethod) ancak böyle bir yaklaşımı kolaylaştırmak için herhangi bir dil desteğini bilmiyorum. Sanal bir korumalı olmayan bir yönteme ve aynı imzalı bir sanal yöntem için "tek satırlı" bir sanal yöntem gerektirmeden (bir satırdan çok daha fazla zaman alan) bir kod bloğu eklemenin temiz bir yolu olsaydı kaynak kodunda).
supercat

MI'da sınıfların temel yöntem olarak adlandırılmadığı ve buna ihtiyaç duyulmasının özel bir nedeni olmadığı için doğal bir gereklilik yoktur. MI'yi suçlamak biraz garip görünüyor, çünkü bu sorun SI'yi de etkiliyor.
cHao
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.