2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Ruby入門] 12. クラスを拡張する② 特異クラス、特異メソッド、オープンクラス

Last updated at Posted at 2017-04-23

>> 連載の目次は こちら!

クラスの機能拡張の話の続き。
今回は、特異クラス/特異メソッド、オープンクラス について整理してみる。

■ 特異クラスと特異メソッド

●参考URL

↓正直このへん読んでるだけでかなり楽しいです。メタとかもうSF...

・「Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)」
http://magazine.rubyist.net/?0046-SingletonClassForBeginners
Rubyist Magazine の記事

・「Rubyのメタクラス階層」
http://yugui.jp/articles/768
Yugui さんのページ

・「Rubyの特異クラス・特異メソッドについて」
https://allabout.co.jp/gm/gc/453836/
All About の記事

●概要

Rubyでは、すべてのオブジェクトが、それぞれに自分だけのクラス定義空間を持っている。

ややこしいのだが、foo が Fooクラスのインスタンスであるとき、Fooのクラス定義とは別に、fooというクラス定義空間も隠れて存在している。
この、普段は目に見えない、各インスタンス毎の独自のクラス定義空間を、「特異クラス」と呼んでいる。

なんでそんなものがあるかというと、それぞれのインスタンスに独自に機能(メソッド)を持たせることができるからだ。
一般的なオブジェクト指向なら、fooはFooクラスの定義に従うので、勝手にオレオレメソッドなんか持つことはできない。
でもRubyではできる。オレだけのクラス定義空間である「特異クラス」にメソッドを定義すれば良いからだ。

このように、各オブジェクト側で「特異クラス」というメタな空間を利用して独自に定義したオレオレメソッドのことを、「特異メソッド」と呼んでいる。

さらにややこしいのだが、Fooの定義自体もClassクラスのインスタンスである以上、Fooの特異クラスも存在する。
つまり、FooはClassクラスのインスタンスだから、基本的にはClassクラスの定義に従うが、特異メソッドとしてFooだけのオレオレメソッドも持つことができる。
これがつまり、Fooクラスのクラスメソッドということになる。
(前々々回の記事で、クラスメソッドの定義方法をいくつも紹介したが、まさにあれが↑に該当する)

特異クラスは、singleton_class というメソッドで取得することができるので、コードで実際に確認してみる。

class Foo; end

# クラスの定義であるところの「Foo」そのものの特異クラスの存在を確認
p Foo.singleton_class  # (1)  #<Class:Foo>

# Fooを実体化したFooクラスの各インスタンスの特異クラスの存在を確認
f = Foo.new
p f.singleton_class    # (2)  #<Class:#<Foo:0x007fa6ed90a358>>

上記では、
(1) は、Fooという名のClassクラスインスタンスが独自に隠し持っているクラス定義空間(特異クラス)
(2) は、Fooの、とあるインスタンスが独自に隠し持っているクラス定義空間(特異クラス)
が返されており、Fooそのものも、Fooの具体的なインスタンスも、それぞれに特異クラスを持っていることがわかる

●特異クラスの空間へアクセスする

前述のように、クラスそのものも、クラスを実体化した各インスタンスも、それぞれに特異クラスを持っている。
そのため、特異クラスにメソッドを定義することで、クラスそのものや、クラスの各インスタンスに、直接機能を追加することができる。
特異クラスに定義されたメソッドのことを「特異メソッド」と呼ぶ。

ここでは、特異クラスというメタな定義空間へアクセスする方法を整理してみる

class Foo
    p self                  # (1)  Foo
    p Foo                   # (2)  Foo
    p self.class            # (3)  Class
    p self.singleton_class  # (4)  #<Class:Foo>

    def self.my_class_method  # (5)
        p self                # (6)  Foo
    end

    def my_instance_method    # (7)
        p self                # (8)  #<Foo:0x007f810f23b080>
    end

    class << self              # (9)
        p self                 # (10)  #<Class:Foo>
        def my_class_method_2  # (11)
            p self             # (12)  Foo
        end
    end
end

Foo.my_class_method          # (6)   Foo
Foo.new.my_instance_method   # (8)   Class:#<Foo:0x007ff34b864980>>
Foo.my_class_method_2        # (12)  Foo

# 
# クラスの外でFooの特異クラスにアクセスする方法も、基本的には同じ。クラスの外だからselfが使えないだけ。
# ↓ ↓ ↓ ↓ ↓ ↓

foo = Foo.new
def foo.my_instance_method_2  # (13)
    p self                    # (14)  #<Foo:0x007ff7e51dc6a0>
end
foo.my_instance_method_2

def Foo.my_class_method_3    # (15)
    p self                   # (16)  Foo
end
Foo.my_class_method_3

class << foo                 # (17)
    p self                   # (18) #<Class:#<Foo:0x007f92879374e0>>
    def my_instance_method_3 # (19)
        p self               # (20) #<Foo:0x007f91de9c0140>
    end
end
foo.my_instance_method_3

class << Foo                 # (21)
    p self                   # (22) #<Class:Foo>
    def my_class_method_4    # (23)
        p self               # (24) Foo
    end
end
Foo.my_class_method_4

(1) ここ(クラス定義の中、かつメソッドの外)では、selfは、Foo自身のオブジェクトを指している
(2) Foo も同じ
(3) self(Foo) は Classクラスのオブジェクトである
(4) self(Foo) は オレオレな「特異クラス」を持っている

(5) 特異クラス空間へのアクセス方法① self(Foo自身のオブジェクト)に対してメソッドを定義する
==> Fooオブジェクトに特異メソッドを定義 ==> Fooのクラスメソッドを定義
この方法だと、特異クラス空間へメソッドを差し込んでいる感じで、自分がその空間に入っている感じではないな...
(6) Fooのクラスメソッドの中でも同様に、selfは、Foo自身のオブジェクトを指している

(7) これは普通にインスタンスメソッドの定義
(8) インスタンスメソッドの中では、selfは、Fooの具体的なインスタンスを指していることがわかる

(9) 特異クラス空間へのアクセス方法② self(またはクラス名)を引いて特異クラス空間を開き、自分がその中に侵入する
(10) ここはFooの特異クラスの中だから、self もFooの特異クラスを指している
(11) Fooの特異クラスの中でメソッドを定義すると、それはFooのクラスメソッドになる
(12) Fooのクラスメソッドの中では、selfは、Foo自身のオブジェクトを指している

(13) 特異クラス空間へのアクセス方法③ とあるインスタンスのに対してメソッドを定義する
==> とあるインスタンスに対して特異メソッドを定義 ==> そのインスタンス独自のメソッドを定義
これは、前述の(5) 特異クラス空間へのアクセス方法① と基本的には同じ。対象がFoo自身ではなくFooのとあるインスタンスなだけ。
(14) インスタンスの特異クラスのメソッドの中だから、selfはインスタンスを指している

(15) 特異クラス空間へのアクセス方法④ Foo(クラス定義のオブジェクト)に対してメソッドを定義する
==> Fooオブジェクトに特異メソッドを定義 ==> Fooのクラスメソッドを定義
これは、前述の(5) 特異クラス空間へのアクセス方法① とまったく同じ。クラスの外だからselfが使えないだけ。
(16) Fooのクラスメソッドの中では、selfは、Foo自身のオブジェクトを指している

(17) 特異クラス空間へのアクセス方法⑤ とあるインスタンスを引いて特異クラス空間を開き、自分がその中に侵入する
これは、前述の(9) 特異クラス空間へのアクセス方法② と基本的には同じ。対象がFoo自身ではなくFooのとあるインスタンスなだけ。
(18) とあるインスタンスの特異クラスの中だから、selfは、インスタンスの特異クラスを指している
(19) とあるインスタンスの特異クラスにメソッドを定義すると、そのインスタンス独自のメソッドになる
(20) インスタンスの特異クラスのメソッドの中だから、selfはインスタンスを指している

(21) 特異クラス空間へのアクセス方法⑥ クラス名を引いて特異クラス空間を開き、自分がその中に侵入する
これは、前述の(9) 特異クラス空間へのアクセス方法② とまったく同じ。クラスの外だからselfが使えないだけ。
(22) ここはFooの特異クラスの中だから、self もFooの特異クラスを指している
(23) Fooの特異クラスの中でメソッドを定義すると、それはFooのクラスメソッドになる
(24) Fooのクラスメソッドの中では、selfは、Foo自身のオブジェクトを指している

■ オープンクラス

●概要

Rubyでは、既存のクラス定義を開いて、機能を追加したり上書きしたりできる。
String とか Array とかのRubyの組み込みクラスでも変更できる。

便利だが、誰かが組み込みクラスの振る舞いを変えて、後続の処理すべてに影響、なんてこともあり得るし、みんなが好き勝手にやるとあっという間にカオスになって大変そう。

実際の開発案件では禁止されてたりするんじゃないかな。
あるいはアプリケーションの基盤部分だけ、コアなメンバーだけがやるとか。
案件向けに StringUtilとか、XxxStringクラスとか作る代わりに、String自体にメソッドを追加していけば便利っちゃ便利だよな。

●試してみる

class String

    # 新規にメソッドを追加
    def hello
        "hello " + self + " !"
    end

    # 既存メソッドを上書き
    def concat(str)
        res = self + str

        # 桃太郎侍 にしちゃえ! Ψ(`▽´)Ψウケケケケケ
        res + "-zamurai!" if (res = "momotaro")
    end

end

puts "taro".hello           # hello taro !
puts "momo".concat("taro")  # momotaro-zamurai!

●Refinements で影響範囲を限定する

[参考URL]

http://blog.fenrir-inc.com/jp/2014/07/refine.html
http://qiita.com/joker1007/items/68d066a12bc763bd2cb4

[概要]

①モジュール内で「refine オープンしたいクラス名 do 〜 end」で既存クラスを開いて拡張
②拡張機能を使いたい場面で、明示的に「using モジュール名」とすると、そこからは拡張した機能を使える
※「using モジュール名」したあと、同一ファイルのなかでのみ有効になるので、オープンクラスによるクラス拡張の影響範囲は限定的になる

[試してみる]
module ArrayExtensible

  # Array クラスを拡張するよー
  refine Array do

    # 既存メソッドを上書き
    def size()
        # いっぱいあることにしちゃえ! Ψ(`▽´)Ψウケケケケケ
        99999999
    end

  end

end

# using する前...
p [1,2,3].size   # 3
 
using ArrayExtensible

# using した後...
p [1,2,3].size   # 99999999
2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?