yakut mirası ve mixins


127

Ruby'de, birden fazla mixini dahil edebildiğiniz, ancak yalnızca bir sınıfı genişletebildiğiniz için, miras yerine mixinler tercih edilecek gibi görünüyor.

Sorum: Yararlı olması için genişletilmesi / dahil edilmesi gereken bir kod yazıyorsanız, neden onu bir sınıf yapasınız? Ya da başka bir deyişle, neden onu her zaman bir modül yapmıyorsun?

Bir sınıf istemenizin tek bir nedenini düşünebilirim, o da sınıfı somutlaştırmanız gerektiğinde. ActiveRecord :: Base söz konusu olduğunda, bunu asla doğrudan somutlaştırmazsınız. Bunun yerine bir modül olması gerekmez miydi?

Yanıtlar:


176

Ben sadece bu konu hakkında okumak İyi Topraklı Rubyist (Bu arada büyük kitap). Yazar, açıklamakta benden daha iyi bir iş çıkarıyor, bu yüzden ona alıntı yapacağım:


Tek bir kural veya formül her zaman doğru tasarımla sonuçlanmaz. Ancak sınıf ve modül kararları alırken birkaç noktayı aklınızda bulundurmanızda fayda var:

  • Modüllerin örnekleri yoktur. Bu, varlıkların veya nesnelerin genellikle en iyi sınıflarda modellendiğini ve varlıkların veya nesnelerin özelliklerinin veya özelliklerinin en iyi modüller halinde kapsüllendiğini izler. Buna karşılık olarak, bölüm 4.1.1'de belirtildiği gibi, sınıf adları isim olma eğilimindeyken, modül adları genellikle sıfatlardır (Yığın'a karşı Yığın Benzeri).

  • Bir sınıfın yalnızca bir süper sınıfı olabilir, ancak istediği kadar modülü karıştırabilir. Kalıtım kullanıyorsanız, mantıklı bir üst sınıf / alt sınıf ilişkisi oluşturmaya öncelik verin. Bir sınıfın tek ve tek üst sınıf ilişkisini, sınıfa birkaç özellik kümesinden yalnızca biri haline gelebilecek bir şey kazandırmak için kullanmayın.

Bu kuralları bir örnekte özetlemek gerekirse, yapmamanız gerekenler:

module Vehicle 
... 
class SelfPropelling 
... 
class Truck < SelfPropelling 
  include Vehicle 
... 

Aksine, şunu yapmalısınız:

module SelfPropelling 
... 
class Vehicle 
  include SelfPropelling 
... 
class Truck < Vehicle 
... 

İkinci versiyon, varlıkları ve mülkleri çok daha düzgün bir şekilde modeller. Kamyon Araçtan aşağıya iner (bu mantıklıdır), oysa SelfPropelling araçların bir özelliğidir (en azından bu dünya modelinde önem verdiğimiz her şey) - Kamyonun soyundan olması nedeniyle kamyonlara aktarılan bir özelliktir. veya Aracın özel formu.


1
Örnek bunu düzgün bir şekilde gösteriyor - Kamyon Bir Araçtır - Araç olmayacak bir Kamyon yoktur.
PL J

1
Örnek bunu düzgün bir şekilde gösteriyor - TruckIS A Vehicle- Truckbu a olmayacak bir şey yok Vehicle. Yine de modülü arayabilirim belki SelfPropelable(:?) Hmm SelfPropeledkulağa doğru geliyor, ama neredeyse aynı: D. Her neyse, buna dahil etmezdim Vehicleama içinde Truck- çünkü AREN'TEN ARAÇLAR VARDIR SelfPropeled. Ayrıca iyi bir gösterge de sormaktır - OLAN Araçlar DEĞİL, başka Şeyler var SelfPropeledmı? - Belki, ama bulmam daha zor. Böylece VehicleSelfPropelling sınıfından miras alınabilir (sınıf olarak SelfPropeled- bu daha çok bir rol olduğu için buna uymaz )
PL J

39

Bence mixins harika bir fikir, ama burada kimsenin bahsetmediği başka bir sorun var: ad alanı çarpışmaları. Düşünmek:

module A
  HELLO = "hi"
  def sayhi
    puts HELLO
  end
end

module B
  HELLO = "you stink"
  def sayhi
    puts HELLO
  end
end

class C
  include A
  include B
end

c = C.new
c.sayhi

Hangisi kazanır? Ruby'de, module Bdaha sonra dahil ettiğiniz için ikincisi olduğu ortaya çıkıyor module A. Şimdi, bu sorunu önlemek için kolay: emin tüm yapmak module Ave module B'ler sabitleri ve yöntemler olası ad içindedir. Sorun, derleyicinin çarpışmalar olduğunda sizi hiç uyarmamasıdır.

Bu davranışın büyük programcı ekipleri için ölçeklenmediğini savunuyorum - uygulayan kişinin class Ckapsamdaki her ismi bildiğini varsaymamalısınız . Ruby, bir sabiti veya farklı türde bir yöntemi geçersiz kılmanıza bile izin verir . Ben olabilir emin değilim hiç doğru davranış düşünülebilir.


2
Bu akıllıca bir uyarıdır. C ++ 'daki çoklu kalıtım tuzaklarını anımsatır.
Chris Tonkinson

1
Bunun için herhangi bir iyi hafifletme var mı? Bu, Python çoklu kalıtımın üstün bir çözüm olmasının bir nedeni gibi görünüyor (bir dil p * ssing eşleşmesi başlatmaya çalışmak değil; sadece bu belirli özelliği karşılaştırarak).
Marcin

1
@bazz Bu harika ve hepsi, ancak çoğu dilde kompozisyon zahmetlidir. Aynı zamanda çoğunlukla ördek türü dillerde de geçerlidir. Ayrıca tuhaf durumlar yaşamamanızı da garanti etmez.
Marcin

Eski gönderi, biliyorum ama yine de aramalarda çıkıyor. Cevap kısmen yanlıştır - C#sayhiçıktılar B::HELLORuby sabitleri karıştırdığı için değil, Ruby sabitleri yakından uzağa çözdüğü için - bu nedenle içinde HELLOreferans Bher zaman çözümlenir B::HELLO. Bu, C sınıfı da kendi sınıfını tanımlasa bile geçerlidir C::HELLO.
Laas

13

Benim almam: Modüller davranış paylaşımı içindir, sınıflar ise nesneler arasındaki ilişkileri modellemek içindir. Teknik olarak her şeyi bir Object örneği haline getirebilir ve istenen davranış setini elde etmek istediğiniz modülleri karıştırabilirsiniz, ancak bu zayıf, gelişigüzel ve okunamaz bir tasarım olacaktır.


2
Bu, soruyu doğrudan yanıtlar: miras, projenizi daha okunaklı hale getirebilecek belirli bir organizasyon yapısını zorlar.
zımpara

10

Sorunuzun cevabı büyük ölçüde bağlamsaldır. Pubb'ın gözlemini damıtarak, seçim öncelikle değerlendirilen alan tarafından yönlendirilir.

Ve evet, ActiveRecord bir alt sınıf tarafından genişletilmek yerine dahil edilmiş olmalıydı. Başka bir ORM - datamapper - kesinlikle bunu başarır!


4

Andy Gaskell'in cevabını çok beğendim - sadece evet eklemek istedim, ActiveRecord kalıtımı kullanmamalı, bunun yerine davranışı (çoğunlukla kalıcılığı) bir modele / sınıfa eklemek için bir modül içermelidir. ActiveRecord sadece yanlış paradigmayı kullanıyor.

Aynı nedenden dolayı, MongoId'i MongoMapper'dan çok seviyorum, çünkü geliştiriciye problem alanında anlamlı bir şeyi modellemenin bir yolu olarak kalıtımı kullanma şansı bırakıyor.

Rails topluluğundaki hiç kimsenin "Ruby mirasını" kullanılması gerektiği gibi kullanmaması üzücü - sadece davranış eklemek için değil, sınıf hiyerarşilerini tanımlamak için.


1

Mixins'i anlamanın en iyi yolu sanal sınıflardır. Karışımlar, bir sınıfın veya modülün üst zincirine enjekte edilmiş "sanal sınıflardır".

"İnclude" komutunu kullandığımızda ve ona bir modül ilettiğimizde, modülü miras aldığımız sınıfın hemen önüne ata zincirine ekler:

class Parent
end 

module M
end

class Child < Parent
  include M
end

Child.ancestors
 => [Child, M, Parent, Object ...

Ruby'deki her nesnenin ayrıca bir singleton sınıfı vardır. Bu tekil sınıfa eklenen yöntemler doğrudan nesnede çağrılabilir ve bu nedenle "sınıf" yöntemler olarak hareket ederler. Bir nesne üzerinde "expand" kullandığımızda ve nesneye bir modül ilettiğimizde, modülün yöntemlerini nesnenin singleton sınıfına ekliyoruz:

module M
  def m
    puts 'm'
  end
end

class Test
end

Test.extend M
Test.m

Singleton sınıfına singleton_class yöntemi ile erişebiliriz:

Test.singleton_class.ancestors
 => [#<Class:Test>, M, #<Class:Object>, ...

Ruby, sınıflara / modüllere karıştırılırken modüller için bazı kancalar sağlar. includedRuby tarafından sağlanan ve bir modül veya sınıfa bir modül eklediğinizde çağrılan bir kanca yöntemidir. Dahil edildiği gibi, extendeduzatma için ilişkili bir kanca vardır . Bir modül başka bir modül veya sınıf tarafından genişletildiğinde çağrılacaktır.

module M
  def self.included(target)
    puts "included into #{target}"
  end

  def self.extended(target)
    puts "extended into #{target}"
  end
end

class MyClass
  include M
end

class MyClass2
  extend M
end

Bu, geliştiricilerin kullanabileceği ilginç bir model oluşturur:

module M
  def self.included(target)
    target.send(:include, InstanceMethods)
    target.extend ClassMethods
    target.class_eval do
      a_class_method
    end
  end

  module InstanceMethods
    def an_instance_method
    end
  end

  module ClassMethods
    def a_class_method
      puts "a_class_method called"
    end
  end
end

class MyClass
  include M
  # a_class_method called
end

Gördüğünüz gibi, bu tek modül örnek yöntemler, "sınıf" yöntemler ekliyor ve doğrudan hedef sınıf üzerinde hareket ediyor (bu durumda a_class_method () 'u çağırıyor).

ActiveSupport :: Concern bu kalıbı özetler. ActiveSupport :: Concern'i kullanmak için yeniden yazılmış aynı modül:

module M
  extend ActiveSupport::Concern

  included do
    a_class_method
  end

  def an_instance_method
  end

  module ClassMethods
    def a_class_method
      puts "a_class_method called"
    end
  end
end

-1

Şu anda templatetasarım modelini düşünüyorum . Sadece bir modülle doğru gelmez.

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.