web-dev-qa-db-ja.com

ruby継承vsミックスイン

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

私の質問:役に立つように拡張/インクルードしなければならないコードを書いているなら、なぜあなたはそれをクラスにするのでしょうか?または、別の言い方をすれば、なぜそれを常にモジュールにしないのでしょうか?

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

124
Brad Cupit

IjustこのトピックについてはThe Well-Grounded Rubyist(ところで、素晴らしい本)。著者は私が説明するよりも良い仕事をしているので、私は彼を引用します:


単一のルールまたは式が常に適切な設計になるわけではありません。ただし、クラス対モジュールの決定を行う際には、いくつかの考慮事項に留意しておくと便利です。

  • モジュールにはインスタンスがありません。したがって、エンティティまたは物は一般にクラスで最適にモデル化され、エンティティまたは物の特性またはプロパティは、モジュール。同様に、セクション4.1.1で述べたように、クラス名は名詞になる傾向がありますが、モジュール名は多くの場合、形容詞(スタック対スタックライク)です。

  • クラスに含めることができるスーパークラスは1つだけですが、必要な数のモジュールを混在させることができます。継承を使用している場合は、賢明なスーパークラス/サブクラスの関係。クラスの唯一のスーパークラス関係を使い切って、いくつかの特性セットのうちの1つにしかならないものをクラスに与えないでください。

これらのルールを1つの例にまとめると、すべきではないことは次のとおりです。

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

むしろ、これを行う必要があります。

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

2番目のバージョンは、エンティティとプロパティをよりきれいにモデル化します。トラックはVehicleから派生します(これは理にかなっています)が、SelfPropellingはビークルの特性(少なくとも、この世界のモデルで私たちが関心を持っているすべてのもの)です。または車両の特殊な形式。

175
Andy Gaskell

ミックスインは素晴らしいアイデアだと思いますが、ここには別の問題があります。名前空間の衝突です。考慮してください:

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 Bの後に挿入したため、後者のmodule Aになります。これで、この問題を簡単に回避できます。module Aおよびmodule Bのすべての定数とメソッドが、ありそうもない名前空間にあることを確認してください。問題は、衝突が発生してもコンパイラが警告をまったく表示しないことです。

私は、この振る舞いは大規模なプログラマーのチームに拡張できないと主張します。あなたはclass Cを実装している人がスコープ内のすべての名前を知っていると想定すべきではありません。 Rubyは異なるタイプの定数またはメソッドをオーバーライドすることさえできます。everが正しい動作と見なされるかどうかはわかりません。 。

39
Dan Barowy

私の見解:モジュールは動作を共有するためのものであり、クラスはオブジェクト間の関係をモデル化するためのものです。技術的には、すべてをObjectのインスタンスにし、必要な動作セットを取得するために必要なモジュールを混在させることができますが、それは貧弱で、無計画で、やや読めないデザインです。

12
Chuck

あなたの質問への答えは、主に文脈的です。 pubbの観察結果を抽出すると、選択は主に検討中のドメインによって決まります。

そして、はい、ActiveRecordはサブクラスによって拡張されるのではなく、含まれるべきでした。別のORM- datamapper -それを正確に達成します!

10
nareshb

私はAndy Gaskellの答えがとても好きです-はい、ActiveRecordは継承を使用せずに、モデル/クラスに動作(主に永続性)を追加するモジュールを含める必要があることを追加したいだけです。 ActiveRecordは間違ったパラダイムを使用しています。

同じ理由で、MongoMapperよりもMongoIdが非常に好きです。なぜなら、開発者が問題ドメインで意味のある何かをモデル化する方法として継承を使用する機会を残すからです。

Railsコミュニティの誰もが、使用するはずの「Ruby継承」を使用していない-動作を追加するだけでなく、クラス階層を定義するのは悲しいことです。

4
Tilo

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

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

class Parent
end 

module M
end

class Child < Parent
  include M
end

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

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

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は、モジュールがクラス/モジュールに混在している場合、モジュールにいくつかのフックを提供します。 includedは、Rubyによって提供されるフックメソッドです。これは、モジュールまたはクラスにモジュールを含めるたびに呼び出されます。インクルードと同様に、関連付けられた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
1
Donato