web-dev-qa-db-ja.com

Rubyのincludeとextendの違いは何ですか?

Rubyメタプログラミングに頭を悩ませています。 mixin/modulesは常に私を混乱させます。

  • include:指定されたモジュールメソッドをターゲットクラスのinstance methodsとしてミックスします
  • extend:指定されたモジュールメソッドをターゲットクラスのクラスメソッドと混合

それで、大きな違いはこれだけですか、それともより大きなドラゴンが潜んでいますか?.

module ReusableModule
  def module_method
    puts "Module Method: Hi there!"
  end
end

class ClassThatIncludes
  include ReusableModule
end
class ClassThatExtends
  extend ReusableModule
end

puts "Include"
ClassThatIncludes.new.module_method       # "Module Method: Hi there!"
puts "Extend"
ClassThatExtends.module_method            # "Module Method: Hi there!"
392
Gishu

あなたが言ったことは正しいです。しかし、それだけではありません。

クラスKlazzとモジュールModがあり、ModKlazzを含めると、KlazzのインスタンスがModのメソッドにアクセスできます。または、KlazzModで拡張して、classKlazzModのメソッドへのアクセス権を与えることができます。ただし、o.extend Modを使用して任意のオブジェクトを拡張することもできます。この場合、Modと同じクラスを持つ他のすべてのオブジェクトはそうではありませんが、個々のオブジェクトはoのメソッドを取得します。

238
domgblackwell

extend-指定されたモジュールのメソッドと定数をターゲットのメタクラス(つまりシングルトンクラス)に追加します。

  • Klazz.extend(Mod)を呼び出すと、KlazzにはModのメソッドが(クラスメソッドとして)あります
  • obj.extend(Mod)を呼び出すと、objにはModのメソッドが(インスタンスメソッドとして)ありますが、obj.classの他のインスタンスにはこれらのメソッドが追加されていません。
  • extendはパブリックメソッドです

include-デフォルトでは、指定されたモジュールのメソッドをターゲットモジュール/クラスのインスタンスメソッドとしてミックスします。例えば.

  • class Klazz; include Mod; end;を呼び出すと、Klazzのすべてのインスタンスが(インスタンスメソッドとして)Modのメソッドにアクセスできるようになります。
  • includeは、コンテナクラス/モジュール内から呼び出されることを目的としているため、プライベートメソッドです。

ただし、、モジュールは非常に頻繁にoverrideincludeの動作をモンキーパッチでincluded 方法。これは、従来のRailsコードで非常に顕著です。 Yehuda Katzからの詳細

includeの詳細、およびデフォルトの動作(次のコードを実行したと仮定)

class Klazz
  include Mod
end
  • ModがすでにKlazzまたはその祖先の1つに含まれている場合、includeステートメントは効果がありません
  • また、衝突しない限り、KlazzのModの定数も含まれます。
  • KlazzはModのモジュール変数にアクセスできます。 @@fooまたは@@bar
  • 循環インクルードがある場合、ArgumentErrorを発生させます
  • モジュールを呼び出し元の直接の祖先として接続します(つまり、ModをKlazz.ancestorsに追加しますが、ModはKlazz.superclass.superclass.superclassのチェーンに追加されません。したがって、Klazz#fooでsuperを呼び出すと、 Klazzの実際のスーパークラスのfooメソッドをチェックする前にMod#foo。詳細についてはRubySpecを参照してください。

もちろん、 Ruby core documentation は常にこれらのことを行うのに最適な場所です。 RubySpecプロジェクト も機能を正確に文書化したため、素晴らしいリソースでした。

308
John Douthat

そのとおりです。

舞台裏では、includeは実際にはappend_featuresのエイリアスです(ドキュメントから):

Rubyのデフォルトの実装では、このモジュールがaModuleまたはその祖先のいずれかにまだ追加されていない場合、このモジュールの定数、メソッド、およびモジュール変数をaModuleに追加します。

14
Toby Hede

RubySpecsを使用したDigのヒントなど、他のすべての回答は良好です。

https://github.com/rubyspec/rubyspec/blob/master/core/module/include_spec.rb

https://github.com/rubyspec/rubyspec/blob/master/core/module/extend_object_spec.rb

ユースケースに関して:

ClassThatIncludesクラスのReusableModuleモジュールincludeを使用すると、メソッド、定数、クラス、サブモジュール、およびその他の宣言が参照されます。

モジュールReusableModuleを使用してClassThatExtendsクラスextendを実行すると、メソッドと定数はcopied。明らかに、注意を怠ると、定義を動的に複製することで多くのメモリを浪費する可能性があります。

ActiveSupport :: Concernを使用する場合、.included()機能により、インクルードクラスを直接書き換えることができます。 Concern内のClassMethodメソッドは、インクルードクラスにextended(コピー)を取得します。

3
Ho-Sheng Hsiao

また、動作するメカニズムを説明したいと思います。私が正しくない場合は修正してください。

includeを使用するときは、クラスからいくつかのメソッドを含むモジュールにリンケージを追加しています。

class A
include MyMOd
end

a = A.new
a.some_method

オブジェクトにはメソッドがなく、クラスとモジュールのみがあります。 aがメッセージを受信するとsome_methodaの固有クラスで検索メソッドsome_methodを開始し、その後AクラスでAにリンクしますクラスモジュールがある場合(逆の順序で、最後に含まれたものが優先).

extendを使用すると、オブジェクトの固有クラスのモジュールにリンケージを追加します。したがって、A.new.extend(MyMod)を使用する場合、モジュールのリンクをAのインスタンス固有クラスまたはa'クラスに追加します。 A.extend(MyMod)を使用する場合、A(オブジェクトのクラスはオブジェクトでもあります)eigenclass A'にリンケージを追加します。

したがって、aのメソッド検索パスは次のとおりです。a=> a '=> a'クラスにリンクされたモジュール=> A.

ルックアップパスを変更するprependメソッドもあります。

a => a '=> Aに追加されたモジュール=> A => Aに含まれたモジュール

私の悪い英語でごめんなさい。

1
user1136228