Rubyでクラス、インスタンス、メタクラスを解明する

Ruby Magicの新しいエピソードへようこそ! 今月号はmetaclasses、更に端を発した議論と開発(こんにちはMaud!).

メタクラスを調べることで、クラスとインスタンスメソッドがRubyでどのように機能するかを学びます。 途中で、明示的な”definee”を渡してメソッドを定義することと、class << selfまたはinstance_evalを使用することの違いを発見してください。 行くぞ!

クラスインスタンスとインスタンスメソッド

Rubyでメタクラスが使用されている理由を理解するために、インスタンスメソッドとクラスメソ

Rubyでは、クラスは他のオブジェクトを作成するための青写真を定義するオブジェクトです。 クラスは、そのクラスの任意のインスタンスで使用可能なメソッドを定義します。

クラス内でメソッドを定義すると、そのクラスにインスタンスメソッドが作成されます。 そのクラスの将来のインスタンスには、そのメソッドが使用可能になります。

1 2 3 4 5 6 7 8 9 10 11 12
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs" 

この例では、Userという名前のクラスを作成し、#nameという名前のインスタンスメソッドを使用して、ユーザーの名前を返します。 クラスを使用して、クラスインスタンスを作成し、それをuserという名前の変数に格納します。 userUserクラスのインスタンスであるため、#nameメソッドが使用可能です。

クラスは、そのインスタンスメソッドをメソッドテーブルに格納します。 そのクラスの任意のインスタンスは、そのインスタンスメソッドにアクセスするためにそのクラスのメソッドテーブルを参照します。

クラスオブジェクト

クラスメソッドは、最初にインスタンスを作成することなく、クラス上で直接呼び出すことができるメソッドです。 クラスメソッドは、定義時に名前の前にself.を付けることによって作成されます。

クラス自体はオブジェクトです。 定数はクラスオブジェクトを参照するため、そのオブジェクトで定義されたクラスメソッドは、アプリケーションのどこからでも呼び出すことができ

1 2 3 4 5 6 7 8 9
class User # ... def self.all end end User.all # => 

self.-接頭辞で定義されたメソッドは、クラスのメソッドテーブルに追加されません。 それらは代わりにクラスのメタクラスに追加されます。

メタクラス

クラスとは別に、Rubyの各オブジェクトには隠されたメタクラスがあります。 メタクラスはシングルトンであり、単一のオブジェクトに属することを意味します。 クラスの複数のインスタンスを作成すると、それらは同じクラスを共有しますが、すべて別々のメタクラスがあります。

1 2 3 4 5 6 7 8 9
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>> 

この例では、各オブジェクトにクラスUserがありますが、それらのシングルトンクラスには異なるオブジェクトIdがあり、別々のオブジェクトであるこ

メタクラスにアクセスすることで、Rubyは既存のオブジェクトにメソッドを直接追加することができます。 そうすることで、オブジェクトのクラスに新しいメソッドは追加されません。

1 2 3 4 5 6 7 8
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>) 

この例では、robert変数に格納されているユーザーに#last_nameを追加します。 robertUserのインスタンスですが、Userの新しく作成されたインスタンスは、robertのメタクラスにのみ存在するため、#last_nameメソッドにアクセスできません。

自己とは何ですか?

メソッドを定義してreceiverを渡すと、新しいメソッドがクラスのメソッドテーブルに追加されるのではなく、receiverのメタクラスに追加されます。

1 2 3 4 5
tom = User.new("Tom") def tom.last_name "de Bruijn" end 

上記の例では、メソッドを定義するときにtomを受信者として渡すことによって、#last_nametomオブジェクトに直接追加しました。

これはクラスメソッドの動作方法でもあります。

1 2 3 4 5 6 7
class User # ... def self.all end end 

ここでは、.allメソッドを作成するときに、selfを受信者として明示的に渡します。 クラス定義では、selfはクラス(この場合はUser)を参照するため、.allメソッドはUserのメタクラスに追加されます。

Userは定数に格納されているオブジェクトなので、参照するたびに同じオブジェクトと同じメタクラスにアクセスします。

メタクラスを開く

クラスメソッドはクラスオブジェクトのメタクラス内のメソッドであることを学びました。 これを知って、私たちはあなたが前に見たかもしれないクラスメソッドを作成するいくつかの他のテクニックを見てみましょう。

class<<self

少しスタイルが外れていますが、一部のライブラリではclass << selfを使用してクラスメソッドを定義しています。 この構文トリックは、現在のクラスのメタクラスを開き、それと直接対話します。

1 2 3 4 5 6 7 8 9 10 11
class User class << self self # => #<Class:User> def all end end end User.all # => 

この例では、Userのメタクラスにメソッドを追加して、User.allという名前のクラスメソッドを作成します。 前に見たようにメソッドのreceiverを明示的に渡す代わりに、User自体ではなくselfUserのメタクラスに設定しました。

前に学んだように、明示的な受信者のないメソッド定義は、現在のクラスのインスタンスメソッドとして追加されます。 ブロック内では、現在のクラスはUserのメタクラス(#<Class:User>)です。

instance_eval

別のオプションは、instance_evalを使用することです。 クラスのメタクラスはブロック内で定義されたメソッドを受け取りますが、selfはメインクラスへの参照のままです。

1 2 3 4 5 6 7 8 9 10 11
class User instance_eval do self # => User def all end end end User.all # => 

この例では、前と同じようにUserのメタクラスにインスタンスメソッドを定義しますが、selfはまだUserを指しています。 通常は同じオブジェクトを指していますが、”default definee”とselfは異なるオブジェクトを指すことができます。

私たちが学んだこと

私たちは、クラスがメソッドを持つことができる唯一のオブジェクトであり、インスタンスメソッドが実際にはオブジェクトのメタクラス上のメソッドであることを学びました。 class << selfは単にselfを入れ替えてメタクラスでメソッドを定義できることを知っていますが、instance_evalはほとんど同じことをします(ただしselfに触れることはありません)。

メタクラスでは明示的には動作しませんが、Rubyはそれらをボンネットの下で広範囲に使用します。 メソッドを定義したときに何が起こるかを知ることは、Rubyがなぜそのように動作するのか(そしてなぜクラスメソッドの前にself.を付ける必要があるのか)を理解するのに役立ちます。

読んでくれてありがとう。 あなたが読んだものが好きなら、あなたは私たちが月に一度についての新しい記事を公開するときに電子メールを受け取るためにRuby Magicを購読するこ



+