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

ruby の OOP をひと通り見ておこうかな、とか。

ruby

タイムラインに名言が。

... はい。ruby にもクラス書いたり何だりと OOP のための仕組みがありますので、それらをざっと見ていました。

1. クラス名は大文字から始まる。

class Point; end
# ok

class point; end
# ng -> class/module name must be CONSTANT

 Point という名前のクラスは後から定義し直すことはできません。「ある名前があって、それが何であるかを上書き変更できない」という性質は定数と同じですね。ruby の定数には識別子を大文字で始めるというルールがあるのですがそのコンセプトはクラス名(やモジュール名)においても同じ、という理解で良さそうです。

2. メンバは外部公開しない。
 いわゆるメンバ変数は外部からはアクセスできません。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end
end

p = Point.new(100,200)
puts p.x # ng -> undefined method `x'

 エラーメッセージを読めば分かりますが、これはメンバ変数が private 指定されているからエラーになっている、というわけではありません。x というメソッドが無いからエラーになっています。↓このようにアクセサメソッドを定義すれば良いです。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end

  def x=(x) # setter
    @x = x
  end

  def x() # getter
    @x
  end
end

p = Point.new(100,200)
puts p.x # ok -> 100

 メソッドのアクセス修飾はデフォルトで public なのでこれで動きます。
 アクセサメソッドの内容を自分で書きたい場合というのは少ないうえ、アクセサがアクセサ以外のメソッドと混ざって並ぶのは見にくいので、↓略記する機能が用意されています。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end
  attr_accessor :x, :y
end

p = Point.new(100,200)
p.x = 150
puts p.x

 attr_accessor 以外に getter だけを定義する attr_reader, setter だけを定義する attr_writer というのもあるようです。
 ここで注目すべきは 「:x, :y」という風にシンボル記法が使われていることです。「アクセサメソッドがシンボル使って定義されるなら、アクセサ以外のメソッドもシンボルで良くね?」とギモンに思ったのですが、調べてみるとなるほど...。そもそも attr_accessor というのは ruby の文法ではなく、「シンボルを受け取ってメソッドを定義する」関数だそうです。
 この仕組については、↓ここの説明が短くてわかりやすかったですね。
 『まつもと直伝 プログラミングのオキテ 第6回』IT Pro (注: 2007/06/11 の記事)

3. 継承 〜初期化〜
 継承は「<」を使って書きます。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end
  attr_accessor :x, :y
end

class Circle < Point
  def initialize(r=1)
    @r = r
  end
end

r = Circle.new
r.x = 100
puts r.x

 ↑このとき、Point.initialize の引数 x, y はそれぞれデフォルト指定したものが渡されます。もし仮にデフォルト引数が指定されていない場合は↓こう書きます。

class Point
  def initialize(x,y) # デフォルト引数なし
    @x = x
    @y = y
  end
  attr_accessor :x, :y
end

class Circle < Point
  def initialize(r=1)
    super(100,100)	# 明示的な呼び出し
    @r = r
  end
end

r = Circle.new
puts r.x

 ↑ super は基底クラスの同名メソッドを呼び出すための構文です。このとき、もし super(100,100) が無いと Point.initialize が呼ばれないうえ、それだけでは実行時エラーになりません。また super は何度でも使えるので Circle.initialize の中で Point.initialize を何度も呼ぶこともできちゃいます。

4. 継承 〜メソッドのオーバーライド〜
 特にオーバーライド用のキーワードを使う必要はなく、同名のメソッドを定義すれば OK です。インスタンスの状態を文字列にする to_s メソッドをオーバーライドしてみました↓。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end

  def to_s(use_brackets)
    if use_brackets then
      "(#{@x},#{@y})"
    else
      "#{@x},#{@y}"
    end
  end
  attr_accessor :x, :y
end

class Circle < Point
  def initialize(r=1)
    super(100,100)
    @r = r
  end

  def to_s
    '(' + super(false) + ",#{@r})"
  end
end

p = Point.new
puts p.to_s(true) # ok -> (0,0)

r = Circle.new
puts r.to_s # ok -> (100,100,1)

 素直な挙動ですね。オーバーライドは名前のみによって行われるみたいですね。例えば↓こうするとエラーになります。

r = Circle.new
puts r.to_s(true) # ng -> wrong number of arguments (1 for 0) (ArgumentError)

 従って、オーバーライドしつつデフォルト引数を継承することも不可です。

5. 継承 〜アクセス修飾〜
 継承によってアクセス修飾を変えることができるようです。

class Point
  def initialize(x=0,y=0)
    @x = x
    @y = y
  end
  attr_accessor :x, :y
  private :x
end

class Circle < Point
  def initialize(r=1)
    @r = r
  end		
  public :x
end

p = Point.new
puts p.x # ng -> private method `x' called

r = Circle.new
puts r.x # ok

 ↑このように、基底クラスで private 指定したメソッドを派生クラスで public にすることもできます。(Java ではこれは不可ですね。逆に基底クラスで public であるメソッドを派生クラスで private にすることは許されていますけど。)
 
6. ギモン
 ちゃんとした資料を当たらずにやってるのでいくつか分からないことが。時間があるときに確認するつもり。(本稿で言及が無いことへの言い訳)


a.クラスメソッドのアクセス修飾を private, protected に変更できない?
b.オーバーライドしたメソッド foo(), bar() があるとき、派生クラスの bar() から基底クラスの foo() を呼びだすことはできない?
c.クラス A, B, C があり A < B < C という継承関係になっていて、それぞれでメソッド foo() が定義されているとする。このとき A.foo から C.foo を直接呼び出すことはできない?
d.抽象メソッドという概念は無い?
7. 雑感
 別に JavaC++ と比較して重箱の隅をつつくつもりは無いんですけども、僕が使っている他の言語と比較して見てゆくと小さい差異が見つかって面白いですね。ruby とか Python のコードって何故か簡潔に見えるのですが、その理由のいくらかは言語仕様にあるなーというのをありありと感じます。