Rubyでは、複数のミックスインを含めることができますが、1つのクラスのみを拡張できるため、継承よりもミックスインが優先されるようです。
私の質問:役に立つように拡張/インクルードしなければならないコードを書いているなら、なぜあなたはそれをクラスにするのでしょうか?または、別の言い方をすれば、なぜそれを常にモジュールにしないのでしょうか?
クラスが必要な理由は1つしかありません。それは、クラスをインスタンス化する必要がある場合です。ただし、ActiveRecord :: Baseの場合、直接インスタンス化することはありません。代わりにモジュールではなかったでしょうか?
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はビークルの特性(少なくとも、この世界のモデルで私たちが関心を持っているすべてのもの)です。または車両の特殊な形式。
ミックスインは素晴らしいアイデアだと思いますが、ここには別の問題があります。名前空間の衝突です。考慮してください:
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が正しい動作と見なされるかどうかはわかりません。 。
私の見解:モジュールは動作を共有するためのものであり、クラスはオブジェクト間の関係をモデル化するためのものです。技術的には、すべてをObjectのインスタンスにし、必要な動作セットを取得するために必要なモジュールを混在させることができますが、それは貧弱で、無計画で、やや読めないデザインです。
あなたの質問への答えは、主に文脈的です。 pubbの観察結果を抽出すると、選択は主に検討中のドメインによって決まります。
そして、はい、ActiveRecordはサブクラスによって拡張されるのではなく、含まれるべきでした。別のORM- datamapper -それを正確に達成します!
私はAndy Gaskellの答えがとても好きです-はい、ActiveRecordは継承を使用せずに、モデル/クラスに動作(主に永続性)を追加するモジュールを含める必要があることを追加したいだけです。 ActiveRecordは間違ったパラダイムを使用しています。
同じ理由で、MongoMapperよりもMongoIdが非常に好きです。なぜなら、開発者が問題ドメインで意味のある何かをモデル化する方法として継承を使用する機会を残すからです。
Railsコミュニティの誰もが、使用するはずの「Ruby継承」を使用していない-動作を追加するだけでなく、クラス階層を定義するのは悲しいことです。
ミックスインを理解する最良の方法は、仮想クラスとしてです。ミックスインは、クラスまたはモジュールの祖先チェーンに注入された「仮想クラス」です。
"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