多重継承 - ruby 継承::




ルビーの継承とミックスイン (5)

Rubyでは、複数のミックスインを含めることができますが、1つのクラスだけを拡張することができるため、継承よりもミックスインが優先されるようです。

私の質問:あなたが拡張/インクルードする必要があるコードを書く場合、なぜそれをクラスにするのですか? 別の言い方をすれば、なぜあなたはいつもそれをモジュールにしませんか?

クラスをインスタンス化する必要がある場合は、クラスを必要とする理由の1つしか考えられません。 ただし、ActiveRecord :: Baseの場合、直接インスタンス化することはありません。 代わりにモジュールではないでしょうか?


あなたの質問への答えは主に文脈上のものです。 パブの観察を逸脱して、選択は主に考慮中の領域によって駆動される。

ActiveRecordは、サブクラスによって拡張されるのではなく、インクルードされているはずです。 別のORM - datamapper - これを正確に実現します!


ミックスインは素晴らしいアイデアだと思いますが、名前空間の衝突という誰も言及していない別の問題があります。 検討してください:

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

どちらが勝ちますか? Rubyでは、 module A後にmodule Aを組み込んでいるので、後者のmodule Bが分かります。 今、この問題を避けるのは簡単です: module Amodule Bの定数とメソッドのすべてがネームスペースにないことを確認してください。 問題は、衝突が発生したときにコンパイラが警告しないことです。

私は、この振る舞いはプログラマーの大規模なチームには適用されないと主張します。 class C実装する人が範囲内のすべての名前を知っていると仮定しないでください。 Rubyでは、 異なる型の定数またはメソッドをオーバーライドさせることさえできます。 それが正しい挙動と考えられるかどうかはわかりません。


私がmixinを理解する最良の方法は、仮想クラスです。 ミックスインは、クラスまたはモジュールの祖先チェーンに注入された「仮想クラス」です。

「インクルード」を使用してモジュールを渡すと、継承元のクラスの直前に祖先チェーンにモジュールが追加されます。

class Parent
end 

module M
end

class Child < Parent
  include M
end

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

Rubyのすべてのオブジェクトにはシングルトンクラスもあります。 このシングルトンクラスに追加されたメソッドは、オブジェクトに対して直接呼び出すことができるため、「クラス」メソッドとして機能します。 オブジェクトに「extend」を使用してモジュールにオブジェクトを渡すときは、モジュールのメソッドをオブジェクトのシングルトンクラスに追加します。

module M
  def m
    puts 'm'
  end
end

class Test
end

Test.extend M
Test.m

singleton_classメソッドでシングルトンクラスにアクセスできます:

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

Rubyは、モジュールがクラス/モジュールに混在しているときに、モジュールのフックを提供します。 ルビが提供するフックメソッドは、あるモジュールまたはクラスにモジュールを含めるたびに呼び出されます。 含まれているように、 extendedための関連するextendedフックがあります。 モジュールが別のモジュールまたはクラスによって拡張されたときに呼び出されます。

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

これにより、開発者が使用できる興味深いパターンが作成されます。

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

ご覧のとおり、この単一のモジュールは、インスタンスメソッド、 "クラス"メソッドを追加し、ターゲットクラス(この場合はa_class_method()を呼び出します)に直接作用します。

ActiveSupport :: Concernはこのパターンをカプセル化します。 ActiveSupport :: Concernを使うために書き直された同じモジュールがあります:

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

私の取り組み:モジュールは振る舞いを共有するためのものですが、クラスはオブジェクト間の関係をモデル化するためのものです。 技術的には、すべてのものをObjectのインスタンスにして、目的の動作セットを取得したいモジュールにミックスすることはできますが、これは貧弱で、はるかに読みにくく、読めないデザインです。


私はAndy Gaskellの答えが大好きです。ちょうどそれを追加したいだけです.ActiveRecordは継承を使うべきではなく、むしろモデル/クラスに振る舞い(主に永続性)を追加するモジュールを含みます。 ActiveRecordは単に間違ったパラダイムを使用しています。

同じ理由で、私はMongoMapperをMongoMapperに非常に似ています。なぜなら、問題領域で意味のある何かをモデリングする方法として継承を利用できるからです。

Railsコミュニティの誰も、振る舞いを追加するだけでなく、クラス階層を定義するために、Rubyの継承(継承)を使用する方法はほとんどありません。





mixins