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


415

Rubyメタプログラミングに頭を悩ませています。ミックスイン/モジュールは常に私を混乱させます。

  • include:指定したモジュールメソッドをターゲットクラスのインスタンスメソッドとしてミックスします。
  • 延び:ミックスとして指定されたモジュール方式でクラスメソッドターゲットクラスの

大きな違いはこれですか、それとも大きなドラゴンが潜んでいますか? 例えば

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!"

このリンクもチェックしてください:juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Donato

回答:


249

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

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


324

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

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

含める -デフォルトでは、ターゲット・モジュール/クラスのインスタンスメソッドとして指定されたモジュールの方法で混合します。例えば

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

ただし、モジュールは、多くの場合 includeincludedメソッドをサルパッチングすることによっての動作をオーバーライドします。これはレガシーRailsコードで非常に顕著です。イェフダ・カッツからの詳細

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のチェーンに追加されません。したがって、superKlazz#foo で呼び出すと、チェックする前にMod#fooがチェックされますKlazzの実際のスーパークラスのfooメソッドに追加します。詳細はRubySpecを参照してください。)

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


22
私はこれがかなり古い投稿であることを知っていますが、返信が明確であるため、コメントを控えられませんでした。素敵な説明をありがとうございました。
MohamedSanaulla 2011

2
@anwar当然ですが、コメントできるようになり、なんとか記事を見つけることができました。それはここにあります:aaronlasseigne.com/2012/01/17/explaining-include-and-extend と私はまだスキーマが理解をはるかに容易にすると思います
systho

1
この応答の大きな利点は、使用率に応じて、extendクラスまたはインスタンスメソッドとしてメソッドを適用する方法です。Klass.extend=クラスメソッド、objekt.extend=インスタンスメソッド。私は常に(誤って)クラスメソッドがからextend、インスタンスがから来ていると想定していましたinclude
Frank Koehl、2018

16

そのとおりです。

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

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


5

あなたの場合はinclude、クラスにモジュール、モジュールのメソッドは次のようにインポートされているインスタンスメソッド

ただし、extendモジュールをクラスに入れると、モジュールメソッドはクラスメソッドとしてインポートされます。

たとえば、Module_test次のように定義されたモジュールがあるとします。

module Module_test
  def func
    puts "M - in module"
  end
end

さて、includeモジュールについて。クラスAを次のように定義すると、

class A
  include Module_test
end

a = A.new
a.func

出力は次のようになります。 M - in module

私たちはラインを交換する場合include Module_testextend Module_testして、再度コードを実行し、我々は次のエラーが表示されます。undefined method 'func' for #<A:instance_num> (NoMethodError)

メソッド呼び出しa.funcをに変更するA.funcと、出力は次のように変わります。M - in module

上記のコード実行から、includeモジュールの場合、そのメソッドはインスタンスメソッドになりextend、モジュールの場合、そのメソッドはクラスメソッドになることが明らかです。


3

RubySpecsを掘り下げるためのヒントを含め、他のすべての回答は適切です。

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、メソッド、定数、クラス、サブモジュール、および他の宣言を参照します。

あなたがいる場合拡張モジュールReusableModuleでクラスClassThatExtendsを、その後、メソッドと定数を取得しますコピー。明らかに、注意しないと、定義を動的に複製することで大量のメモリを浪費する可能性があります。

ActiveSupport :: Concernを使用する場合、.included()機能を使用すると、包含クラスを直接書き換えることができます。Concern内のモジュールClassMethodsは、包含クラスに拡張(コピー)されます。


1

それが機能するメカニズムについても説明したいと思います。私が正しくない場合は修正してください。

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

class A
include MyMOd
end

a = A.new
a.some_method

オブジェクトにはメソッドがなく、クラスとモジュールだけが持っています。したがって、aメッセージを受信some_methodするとsome_methodaの固有クラスで検索メソッドを開始し、次に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に

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.