Scala'da öz-tipler ve sürekli miras arasındaki fark nedir?


9

Google'da bu konuya ilişkin birçok yanıt ortaya çıkıyor. Ancak, hiçbirinin bu iki özellik arasındaki farkı göstermek için iyi bir iş yaptığını hissetmiyorum. Bu yüzden bir kez daha denemek istiyorum, özellikle ...

Miras ile değil, kendi kendine tiplerle yapılabilecek bir şey nedir?

Bana göre, ikisi arasında ölçülebilir, fiziksel bir fark olmalı, aksi takdirde sadece nominal olarak farklıdırlar.

A Özelliği B'yi veya kendi kendine B tipini genişletiyorsa, her ikisi de B olmanın bir gereklilik olduğunu göstermiyor mu? Fark nerede?


Ödül için belirlediğiniz şartlara karşı temkinli davranıyorum. Birincisi, tüm yazılım olduğu göz önüne alındığında, "fiziksel" farkı tanımlayın. Bunun ötesinde, mixins ile oluşturduğunuz herhangi bir kompozit nesne için, muhtemelen miras ile fonksiyonda yaklaşık bir şey yaratabilirsiniz - eğer işlevi sadece görünür yöntemler açısından tanımlarsanız . Farklılaşacakları yer uzatılabilirlik, esneklik ve uyuşabilirliktir.
itsbruce

Farklı ebatlardaki çelik plakalardan oluşan bir ürün yelpazeniz varsa, bir kutu oluşturmak için bunları birbirine cıvatalayabilir veya kaynak yapabilirsiniz. Bir dar bakış açısından, işlevsellikte eşdeğerdir - birinin kolayca yeniden yapılandırılabileceği veya genişletilebileceği ve diğerinin yapılamayacağı gerçeğini göz ardı ederseniz. Kriterleriniz hakkında daha fazla şey söylediyseniz yanlış kanıtlandığım için mutlu olsam da, bunların eşdeğer olduğunu tartışacağınızı hissediyorum.
itsbruce

Genel olarak söylediklerinize aşinayım, ama yine de bu özel durumda farkın ne olduğunu anlamıyorum. Bir yöntemin diğerinden daha genişletilebilir ve esnek olduğunu gösteren bazı kod örnekleri verebilir misiniz? *
Uzatmalı

Tamam, kelle bitmeden önce deneyebileceğimi düşünün;)
itsbruce

Yanıtlar:


11

A özelliği B'yi uzatırsa, A'ya karıştırmak size tam olarak B artı A'nın eklediği veya genişlettiği her şeyi verir . Özellik bir açıkça B olarak yazıldığında kendinden referansı varsa aksine, daha sonra nihai ana sınıfı da B'de karıştırmak gerekir veya B soyundan türü (ve bunu karıştırmak ilk önemli olduğu,).

En önemli fark bu. İlk durumda, kesin B tipi, A noktasında onu genişletir. İkincisi, ana sınıfın tasarımcısı, ana sınıfın oluşturulduğu noktada B'nin hangi sürümünün kullanılacağına karar verir.

Başka bir fark, A ve B'nin aynı isimde yöntemler sağlamasıdır. A'nın B'yi uzatması durumunda, A yöntemi B'leri geçersiz kılar. A'nın B'den sonra karıştığı yerlerde, A yöntemi basitçe kazanır.

Yazılan öz referans size çok daha fazla özgürlük verir; A ve B arasındaki bağlantı gevşek.

GÜNCELLEME:

Bu farklılıkların yararı hakkında net olmadığınız için ...

Doğrudan miras kullanıyorsanız, B + A olan A özelliğini yaratırsınız. İlişkiyi taş olarak belirlediniz.

Yazdığınız bir öz referans kullanıyorsanız, A özelliğinizi C sınıfında kullanmak isteyen herkes

  • B'yi ve ardından A'yı C'ye karıştırın.
  • B ve sonra A alt tipini C'ye karıştırın.
  • A'yı C'ye karıştırın, burada C B'nin bir alt sınıfıdır.

Ve bu, Scala'nın yapıcı olarak bir kod bloğu ile doğrudan bir özelliği başlatmanıza izin verdiği şekilde, seçeneklerinin sınırı değildir.

A'nın kazanma yöntemi arasındaki farka gelince , A, A genişletme B'ye kıyasla son olarak karıştırıldığı için bunu düşünün ...

Bir dizi özellikte karıştırdığınızda, yöntem foo()her çağrıldığında, derleyici aramak için karıştırılan son özelliğe gider foo(), daha sonra (bulunamazsa), diziyi uygulayan foo()ve kullanan bir özellik bulana kadar sola doğru hareket eder. söyledi. A, arama yapma seçeneğine de sahiptir super.foo(), bu da bir uygulama bulana kadar diziyi sola doğru hareket ettirir, vb.

Yani A'nın B'ye yazdığı bir öz referansı varsa ve A'nın yazarı B'nin uyguladığını bilirse foo(), A super.foo()başka bir şey sağlamazsa foo()B'nin bileceğini söyleyebilir . Bununla birlikte, C sınıfının yaratıcısı, uygulandığı diğer özellikleri bırakma seçeneğine sahiptir ve foo()bunun yerine A bunu alır.

Yine, bu A genişleyen B'den çok daha güçlü ve daha az sınırlayıcıdır ve doğrudan B'nin sürümünü çağırır foo().


A kazanan ve A geçersiz kılma arasındaki fonksiyonel fark nedir? Her iki durumda da farklı mekanizmalar aracılığıyla A alıyorum? Ve ilk örneğinizde ... İlk paragrafınızda neden A özelliği SuperOfB'yi genişletmiyorsunuz? Her iki mekanizmayı kullanarak da sorunu her zaman yeniden şekillendirebileceğimizi hissettiriyor. Sanırım bunun mümkün olmadığı bir kullanım durumu görmüyorum. Ya da çok fazla şey varsayıyorum.
Mark Canlas

Um, neden olur istediğiniz B tanımlar neye ihtiyacınız varsa, A'dan B'ye bir alt sınıfını uzatmak için? Kendi kendine referans B'yi (veya bir alt sınıfı) mevcut olmaya zorlar, ancak geliştiriciye seçenek verir mi? A özelliğini yazdıktan sonra, B'yi uzattığı sürece yazdıkları bir şeyle karışabilirler.
itsbruce

Farkı netleştirmek için güncellendi.
itsbruce

@itsbruce herhangi bir kavramsal fark var mı? IS-A mı HAS-A mı?
Jas

@Jas A ve B özellikleri arasındaki ilişki bağlamında , kalıtım IS-A olurken, yazılan öz referans HAS-A'yı (bir kompozisyon ilişkisi) verir. Özelliklerin karıştırıldığı sınıf için, sonuç ne olursa olsun IS- A'dır.
itsbruce

0

Ben kendi türünü ayarlama vs genişletirken bazı farklılıklar wrt görünürlük ve "varsayılan" uygulamaları gösteren bazı kod var. Gerçek isim çarpışmalarının nasıl çözüldüğü hakkında daha önce tartışılan bölümleri göstermez, bunun yerine neyin mümkün ve neyin yapılamayacağına odaklanır.

trait A1 {
  self: B =>

  def doit {
    println(bar)
  }
}

trait A2 extends B {
  def doit {
    println(bar)
  }
}

trait B {
  def bar = "default bar"
}

trait BX extends B {
  override def bar = "bar bx"
}

trait BY extends B {
  override def bar = "bar by"
}

object Test extends App {
  // object Thing1 extends A1  // FAIL: does not conform to A1 self-type
  object Thing1 extends A1 with B
  object Thing2 extends A2

  object Thing1X extends A1 with BX
  object Thing1Y extends A1 with BY
  object Thing2X extends A2 with BX
  object Thing2Y extends A2 with BY

  Thing1.doit  // default bar
  Thing2.doit  // default bar
  Thing1X.doit // bar bx
  Thing1Y.doit // bar by
  Thing2X.doit // bar bx
  Thing2Y.doit // bar by

  // up-cast
  val a1: A1 = Thing1Y
  val a2: A2 = Thing2Y

  // println(a1.bar)    // FAIL: not visible
  println(a2.bar)       // bar bx
  // println(a2.bary)   // FAIL: not visible
  println(Thing2Y.bary) // 42
}

Bir önemli fark IMO olmasıdır A1bunun ihtiyacı olduğunu göstermiyor Bsadece olarak görüyor şey A1(yukarı-döküm parçaları gösterildiği gibi). Belirli bir uzmanlaşmanın Bkullanıldığını görecek olan tek kod , oluşturulan tür hakkında açıkça bilen koddur (like Think*{X,Y}).

Başka bir nokta olduğunu A2(uzantılı) aslında kullanacak Bbaşka bir şey ise belirtilmişse A1(kendiliğinden türü) o kullanacağı demiyor Bgeçersiz kılınmadığı sürece nesneler örneklenemez zaman, somut bir B açıkça verilmelidir.

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.