読者です 読者をやめる 読者になる 読者になる

【Ruby】クラスの作り方覚書

ずーっとC Familyな言語ばっかり使っていたので(VBの話はやめよう)クラスを作るだけでもどうやるのか調べないといけません。

面倒なのでずらずらずらっとメモしていきます。

宣言

ふつーにclassです。

class Hoge

end

継承はこんな感じ。

class Hoge < Fuga

end

スコープはメソッドにしか適用できないので、privateなclassはないみたい。

また、abstractもサポートされていない。そもそもインタプリタなので、abstract classや(JavaC#で言う)interfaceで責任を担保することが難しいんだと思う。

コンストラクタはinitializeと言うメソッドを宣言すればOK。こんな長い文字列を打たせるのは多分嫌がらせだと思います。initでよかったじゃん。

class Hoge
  def initialize
  
  end
end

メソッド

宣言はclass内で「def [識別子]」とすればOK。(別にclass内じゃなくてもいいけど)

class Hoge
  def fuga
  
  end
end

スコープを変えたい場合はこんな感じ。

class Hoge
  def fuga
    puts "fuga"
  end
  
  private
  def piyo
    puts "piyo"
  end
end

obj = Hoge.new
obj.fuga
obj.piyo #=> NoMethodError

privateの正体はModule#privateであり、これを呼び出すとそれ以降のメソッドは全部privateになるみたい。

引数としてメソッド名のシンボルを可変長で受け取っており、こんなことも出来る。

class Hoge
  def fuga
    puts "fuga"
  end
  
  def piyo
    puts "piyo"
  end
  
  def foo
    puts "foo"
  end
  
  private :piyo, :foo
end

obj = Hoge.new
obj.fuga
obj.piyo #=> NoMethodError
obj.foo #=> NoMethodError

この方法だと先に全メソッドを宣言しなきゃいけないので、C Familyに慣れてると凄まじくキモいし、メソッドの量が増えたら可読性も悪そうに思える。普通に各メソッドに書かせてくれればいいのにね。

スコープはこの他にpublicprotectedがあります。friendはないんですね。まぁ、滅多なことでは使わないんだけど。

ちなみにオーバーロードに該当するものはない。多分、後に宣言したものが優先される。

class Hoge
  def piyo
    puts "piyo"
  end
  
  def piyo(s)
    puts s
  end
end

hoge = Hoge.new
hoge.piyo #=> ArgumentError
hoge.piyo "piyo" #=> piyo

アクセサ

C#のプロパティみたいなものがあります。

class Hoge
  attr_accessor :fuga
  attr_reader :piyo
  attr_writer :foo
  
  def initialize
    @fuga = "fuga";
    @piyo = "piyo";
    @foo = "foo";
  end
  
end

hoge = Hoge.new

puts hoge.fuga
puts hoge.piyo
puts hoge.foo #=> NoMethodError

hoge.fuga = "fugafuga"
hoge.piyo = "piyopiyo" #=> NoMethodError
hoge.foo = "foobar"

attr_xxもスコープと同じくModuleのメソッドみたいです。シンボルの可変長引数なので、同時に複数宣言することも可能です。

get/setの挙動をちょっと書き換えたり、インスタンス取得時に初期値を与えたりすることはできません。まぁその辺はC#が優秀すぎるとこはあるんですが。

get/setで値を計算したい場合は以下のように書き換えられます。

class Hoge
  
  #getter
  def fuga
    return @fuga + "fuga"
  end
  
  #setter
  def fuga=(val)
    @fuga = val + "fuga"
  end
  
end

hoge = Hoge.new

hoge.fuga = "piyo"

puts hoge.fuga #=> piyofugafuga

特異メソッド / 特異クラス / クラスメソッド

Rubyは特異メソッドと呼ばれるものがあります。

これはある特定の1インスタンスメソッドを定義できると言う中々キモい文法です。

javascriptと同じようにメソッド名とメソッドのハッシュを自由に生やせると思っておけば、多少キモさも薄らぐでしょう。

o = Object.new

def o.piyo
  puts "piyo"
end

o.piyo #=> piyo

特異クラスと呼ばれる構文を使用すると、同時に複数定義できます。

o = Object.new

class << o
  def piyo
    puts "piyo"
  end
  
  def fuga
    puts "fuga"
  end
end

o.piyo #=> piyo
o.fuga #=> fuga

この特異ほにゃららでのselfが示すオブジェクトは、指定したオブジェクト自身です。なので、アクセサから変数にアクセスしたり、メソッドを呼び出すことが出来ます。

ちょっとわかりにくいですね。つまりこんなことが出来ます。

class Hoge
  attr_accessor :fuga
  
  def initialize
    @fuga = "fuga";
  end
  
  def foo(s)
    puts s
  end
  
end

hoge = Hoge.new
hoge.fuga ="piyo"

def hoge.new_fuga
  puts "new" + self.fuga
end

def hoge.new_foo
  self.foo "fooooooo"
end

hoge.new_fuga #=> newpiyo

hoge.new_foo #=> fooooooo

これを応用してインスタンスのステートに依存しないメソッドをクラスに定義することができます。C Familyはstaticなメソッドとして宣言しますが、Rubyはクラスメソッドと呼んでいます。別にどっちでもいいんじゃないですかね。

class Hoge
  def self.fuga
    puts "fuga"
  end
end

obj = Hoge.new
obj.fuga #=> NoMethodError

Hoge::fuga #=> fuga

クラスメソッドをprivateにするには特異クラス風にクラスメソッドを作る必要があります。

class Hoge
  class << self
    def fuga
      puts "fuga"
    end
    
    def foo
      piyo
    end
    
    private
    def piyo
      puts "piyo"
    end
    
  end
end

Hoge::fuga #=> fuga
Hoge::piyo #=> NoMethodError
Hoge::foo #=> piyo

Hoge::fooからはprivateなHoge::piyoが呼べます。同じスコープですからね。

ちなみに、インスタンスメソッドからクラスメソッドを呼ぶ場合は明示的にクラス名を書く必要があります。面倒ですね。

class Hoge
  class << self
    def fuga
      puts "fuga"
    end
  end
  
  def piyo
    fuga
  end
end

Hoge::fuga #=> fuga

hoge = Hoge.new
hoge.piyo #=> NameError

class Hoge
  class << self
    def fuga
      puts "fuga"
    end
  end
  
  def piyo
    self.fuga
  end
end

Hoge::fuga #=> fuga

hoge = Hoge.new
hoge.piyo #=> NoMethodError
class Hoge
  class << self
    def fuga
      puts "fuga"
    end
  end
  
  def piyo
    Hoge::fuga
  end
end

Hoge::fuga #=> fuga

hoge = Hoge.new
hoge.piyo #=> fuga

メソッドのスコープ(privateとprotected)

こう、JavaとかC#とかC++とかやってると、privateは「そのクラスでのみ参照できる(継承したクラスからでもダメ)」で、protectedは「そのクラスかそれを継承したクラスでのみ参照できる」って思うんですが、Rubyは違うみたいです。どうもSmalltalkの文法をベースにしているみたいですね。

うだうだ言うより例を見せたほうが早いでしょう。p_hoge_methodはHogeクラスのprivateなメソッドですが、Hogeを継承したFugaクラスのメソッドからは呼び出せます。

class Hoge
  private
  def p_hoge_method
    puts "this method is private"
  end
end

class Fuga < Hoge
  def hoge_method_wrap
    p_hoge_method
  end
end

fuga = Fuga.new
fuga.hoge_method_wrap #=> this method is private
fuga.p_hoge_method #=> NoMethodError

また、Overrideも出来ます。

class Hoge
  private
  def p_hoge_method
    puts "this method is private"
  end
end

class Fuga < Hoge
  def p_hoge_method
    super
  end
end

fuga = Fuga.new
fuga.p_hoge_method #=> this method is private

と言うわけで、Rubyのprivateはおおよそぼくらのprotectedです。

じゃあRubyのprotectedは何なの?って話になるんですが、これはちょっとリファレンスの説明を読んでみましょうか。

メソッドは public、private、protected の三通りの呼び出し制限を持ちます。
  • public に設定されたメソッドは制限なしに呼び出せます。
  • private に設定されたメソッドは関数形式でしか呼び出せません。
  • protected に設定されたメソッドは、そのメソッドを持つオブジェクトがselfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。

「そのメソッドを持つオブジェクトがselfであるコンテキスト」がキモみたいですね。

とりあえずprivateと同じことをやってみましょう。

class Hoge
  protected
  def p_hoge_method
    puts "this method is protected"
  end
end

class Fuga < Hoge
  def hoge_method_wrap
    p_hoge_method
  end
end

fuga = Fuga.new
fuga.hoge_method_wrap #=> this method is protected
fuga.p_hoge_method #=> NoMethodError

呼べますね。じゃあ次。

class Hoge
  protected
  def p_hoge_method
    puts "this method is protected"
  end
end

class Fuga < Hoge
  def p_hoge_method
    super
  end
end

fuga = Fuga.new
fuga.p_hoge_method #=> this method is protected

呼べますね。じゃあどこで変わるのか?って話なんですが、特異メソッド / 特異クラスの中で変わってきます。

class Hoge
  protected
  def protect_hoge
    puts "this method is protected"
  end
  
  private
  def private_hoge
    puts "this method is private"
  end
end

hoge = Hoge.new

def hoge.protect
  self.protect_hoge
end

def hoge.private
  self.private_hoge
end

hoge.protect #=> this method is protected
hoge.private #=> NoMethodError

とまぁこんな感じに、protectedなメソッドは「そのメソッドを持つオブジェクトがselfであるコンテキスト」である特異メソッドからは呼び出せますが、privateなメソッドは呼び出せません。

逆に言うと、完全にprivateなメソッドはModuleのメソッドでは実現できないと言うことです。

多分抜け道もあるんじゃないかなって思うんですが、あんまり本気で探していません。これはこれでRubyの仕様であり思想であり美学なのでしょう。それぞれのプログラミング言語が持つ美学から無理に外れようとすると、大概面倒なことになるだけですので…。

まとめ

とりあえず自分がばーっとリファレンスを読んでわかりにくかったところだけ。

Mix-inみたいなクラスより上位の話はまた機会があったらまとめておきたいですね。そんな時間はあるのでしょうか。