特異メソッド

特定のインスタンスだけが実行できるメソッドを特異メソッド、というそうです。特異メソッド以外にも Ruby では"特異"という単語がよくでてくるような気がします。特異メソッドは英語では "Singleton Method"、と呼ばれるようです。

a = "hello"
b = a.dup

def a.to_s
  "The value is '#{self}'"
end

あるいは

class << a
  def to_s
    "The value is '#{self}'"
  end
end

と書けるとのこと。夢の中で見た謎の文法 "class << a" の謎が解けました。

使いどころはまだよく分かっていません。特定のインスタンスの動きをちょっとだけ変えたい、という時などでしょうか。a と b は同じインスタンスだけれども、a の動きだけちょっと変えたい、でも同じインタフェースでそれらは扱いたい、といったケースなど...。どういう場合にそれが発生するか、が問題です。

ちょっと今日は Ruby を勉強している時間があまりありません。残念です。

method_missing と instance_eval を使った Decorator

Kernel#method_missing を使ってみたいです。

method_missing は、任意のクラスに対してメソッドが呼び出されたとき、対象のメソッドがそのクラスのクラス階層のどこにも実装されてなかった場合に呼び出されるメソッド、だそうです。Perl の AUTOLOAD に似ています。

そこで、method_missing を使った Decorator パターンを実装してみました。Dog オブジェクトを渡すとその犬が強くなる StrongDog です。

dog = Dog.new('ポチ')
sdog = StrongDog.new(dog)

dog.bark
sdog.bark

という風に使います。すると、

ポチ: わんわん
ポチ+1: brrr...
ポチ+1: わんわん

という風な結果が返ります。Decorator の StrongDog でくるむと +1 されて(ロングソード+1、のようなイメージです)、威勢がよくなります。クラスの実装は以下のようにしてみました。

class Dog
  attr :name
  def initialize(name)
    @name = name
  end

  def bark
    puts "#@name: わんわん"
  end
end

class StrongDog
  def initialize(dog)
    @dog = dog
    @dog.name << "+1"
  end

  def method_missing(method_id)
    puts "#{@dog.name}: brrr..."
    @dog.instance_eval(method_id.id2name)
  end
end

StrongDog#bark は実装していないので、それが呼び出されると method_missing が呼び出され、前処理を挟んだあと @dog に同名のメソッドに処理を転送します。

instance_eval の理解がまだ不完全ですが、なんとなく使ってみたらうまくいきました。

クラスはオブジェクトであり、クラス名は定数である

ウサギ本を読んでいたところ、先の Object#const_get の理解を助けてくれる解説を見つけました。P.292 の "クラス名は定数である" の箇所です。

また、Ruby のクラスは Class オブジェクトというオブジェクトでした。したがって、クラスメソッドを呼び出すのは Class オブジェクトにメッセージを送信しているということになります。Ruby の特徴である「すべてがオブジェクト」と聞いてすぐ想像するのは Java のプリミティブ型もオブジェクトであるというあたりですが、クラスもオブジェクトであるというのは面白いです。

$ irb
irb(main):001:0> Object.const_get(:String).class
=> Class

たしかに、クラスを取得してそのクラスを調べると Class クラスです。

クラスがオブジェクトであるということは、クラスも他のオブジェクトと同じように扱うことができ、

  • クラスをコピーしたり
  • クラスにメソッドを渡したり
  • クラスを敷きとして使用したり

といったことができるとウサギ本に書いています。

irb#1(main):001:0> def factory(klass, *args)
irb#1(main):002:1> klass.new(*args)
irb#1(main):003:1> end
=> nil
irb#1(main):004:0> factory(String, "Hello")
=> "Hello"
irb#1(main):005:0> factory(Dir, ".")
=> #<Dir:0x31e3c0>

おお。

Klass

"Class" ではなく "Klass" と書くのはなぜ? の答えは、id:drawnboy さんによると

ローカル変数なクラスオブジェクトを指すときに ”class” は予約語なので ”klass” と書くようです。

とのことです。(昨晩、夢の中でまた別の人が同じことを言っていた気がします。) id:drawnbody さんによれば、Klass は Smalltalk 由来かもしれない、とのこと。面白そうでしたので少し Google で調べてみましたが、由来について解説しているページは簡単には見つかりませんでした。残念です。

alias を使った Adapter パターン

ruby には alias 式でメソッド、演算子、グローバル変数、正規表現後方参照の別名を作ることができるようです。Adapter パターン を Ruby っぽくするために alias を使って書き替えてみます。

#!/usr/local/bin/ruby

class Banner
  def initialize(string)
    @string = string
  end

  def show_with_paren
    puts "(#@string)"
  end

  def show_with_aster
    puts "*#@string*"
  end
end

class PrintBanner < Banner
  alias print_weak show_with_paren
  alias print_strong show_with_aster
end

p = PrintBanner.new("Hello")
p.print_weak
p.print_strong

これでうまくいきました。元のコードが Banner#show_with_paren を PrintBanner#print_weak に委譲しているだけだったので alias を使ってみた、というものです。

わざわざ PrintBanner を用意しなくても

class Banner
  alias print_weak show_with_paren
  alias print_strong show_with_aster
end

でも良いかもしれません。後からいくらでもクラスの挙動を変えられるのが動的言語の良いところです。

ところでこの alias、文法的には「新しい名前 古い名前」の順ですが、なんとなく逆の方が直感的な気がします。しかしこの順になっているのは UNIX の alias コマンドを意識しているからなのでしょうか。

Object#const_get

クラス名からクラスを得る方法は、リファレンスマニュアルによると eval を使う方法、Object#const_get を使う方法の二通りがあるようです。

#!/usr/local/bin/ruby

class Player
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
end

player1 = Object.const_get('Player').new('rubyo', 19)
puts player1.name
puts player1.age

player2 = eval('Player').new('rubco', 18)
puts player2.name
puts player2.age

この時クラス名が Net::HTTP のようにネストしている場合の方法についてもマニュアルに解説がありました。

eval で文字列を"コード上のクラス名"として扱うことについては理解できました。一方の const_get は ri すると

------------------------------------------------------- Module#const_get
     mod.const_get(sym)    => obj
------------------------------------------------------------------------
     Returns the value of the named constant in _mod_.

        Math.const_get(:PI)   #=> 3.14159265358979

とありました。named 定数の値を取得する。引数はシンボル。この Math の例を見ると特定のモジュール内で定義されている定数を取得できる、という風に見れます。 Math モジュールの中には PI という定数が定義されていて、その値は 3.14159... であり、それを Math モジュールの外から取得する手段として const_get が用意されている、ということでしょう。

  • Math モジュールの中の
  • PI 定数を取得する
  • その値は 3.14159...

モジュールのクラスに相当する Module クラスには constans というメソッドがあり、これで定数一覧を取得できます。

$ irb
irb(main):001:0> Math.constants
=> ["E", "PI"]

と、確かに Math モジュールには PI 定数があることが分かります。

もとい、

Object.const_get('Player') #=> Player

はどう理解すれば良いかというと、先の Math のものをそのまま当てはめると

  • Object クラスの中の
  • Player 定数を取得する
  • その値は Player というクラス名( 'Player' という文字列ではなく、コード上でクラス名と解釈される Player という値)

と言う風になります。ということは Object クラスに Player という定数が定義されていることになるのでしょう。そこで、

#!/usr/local/bin/ruby

class Player
end

Object.constants.each {|const| puts const }

というコードを書き確かめてみました。

$ ruby const_get.rb | grep 'Player'
Player

確かに Player という定数が見つかりました。一方、class Player を定義していない段階で const_get してみます。

$ irb
irb(main):001:0> Object.const_get(:Player)
NameError: uninitialized constant Player
        from (irb):1:in `const_get'
        from (irb):1
irb(main):002:0> Object.const_get(:String)
=> String

組み込みクラスの String では結果が返るのに対して、定義されていないクラスの Player には結果が返りませんでした。

つまり、Object クラスは、そのアプリケーションの中で扱えるクラスのクラス名を定数として持つような機構になっている、と理解しました。

Abstract Factory パターン

id:hyuki さんのデザインパターン本から Abstract Factory パターンを移植してみます。

#!/usr/local/bin/ruby

class Item
  def initialize(caption)
    @caption = caption
  end
end

class Link < Item
  def initialize(caption, url)
    super(caption)
    @url = url
  end
end

class Tray < Item
  def initialize(caption)
    super(caption)
    @tray = Array.new
  end

  def add(item)
    @tray.push(item)
  end
end

class Page
  def initialize(title, author)
    @title = title
    @author = author
    @content = Array.new
  end
  
  def add(item)
    @content.push(item)
  end

  def output
    self.make_html
  end
end

class Factory
  def Factory.get_factory(classname)
    eval(classname).new
  end
end

class ListFactory < Factory
  def create_link(caption, url)
    ListLink.new(caption, url)
  end

  def create_tray(caption)
    ListTray.new(caption)
  end

  def create_page(title, author)
    ListPage.new(title, author)
  end
end

class ListLink < Link
  def make_html
    return "<li><a href=?"#@url?">#@caption</a></li>?n"
  end
end

class ListTray < Tray
  def make_html
    result = String.new
    result += "<li>#@caption?n"
    result += "<ul>?n"
    @tray.each do |item|
      result += item.make_html
    end
    result += "</ul>?n"
    result += "</li>"
    result
  end
end

class ListPage < Page
  def make_html
    list = @content.map{ |item|  item.make_html }.join('')
    return <<EOS
<html>
<head><title>#@title</title></head>
<body>
<h1>#@title</h1>

<ul>
#{list}
</ul>

<hr><address>#@author</address>
</body>
</html>
EOS
  end
end

if ARGV.size != 1
  puts "Usage: #$0 <classname>"
  exit
end

factory = Factory.get_factory(ARGV.shift)
hatena = factory.create_link("はてな", "http://www.hatena.ne.jp/")
google = factory.create_link("Google", "http://www.google.com/")
yahoo = factory.create_link("Yahoo!", "http://www.yahoo.com/")
yahoo_jp = factory.create_link("Yahoo! Japan", "http://www.yahoo.co.jp/")

yahoo_tray = factory.create_tray("Yahoo!")
yahoo_tray.add(yahoo)
yahoo_tray.add(yahoo_jp)

page = factory.create_page("LinkPage", "るびお")
page.add(hatena)
page.add(google)
page.add(yahoo_tray)

puts page.output

このコードを、引数に "ListFactory" と与えて実行すると、

$ ruby abstract_factory.rb ListFactory
<html>
<head><title>LinkPage</title></head>
<body>
<h1>LinkPage</h1>

<ul>
<li><a href="http://www.hatena.ne.jp/">はてな</a></li>
<li><a href="http://www.google.com/">Google</a></li>
<li>Yahoo!
<ul>
<li><a href="http://www.yahoo.com/">Yahoo!</a></li>
<li><a href="http://www.yahoo.co.jp/">Yahoo! Japan</a></li>
</ul>
</li>
</ul>

<hr><address>るびお</address>
</body>
</html>

となりました。んー、書籍の例は Java の例で、抽象クラスでサブクラスの実装に縛りをかけながら Factory でごっそりそれらを切り替えるというのがとても美しいのですが、縛りをかけられない Ruby ではいまいちしまりがない感じがします。Item や Tray はコンストラクタのみの実装ですし。

そういえば、一つ学びました。

class Factory
  def Factory.get_factory(classname)
    begin
      eval(classname).new
    rescue NameError
      puts "Unknown factory class: #{classname}."
    end
  end
end

クラス名の文字列からインスタンスを生成する方法です。eval で文字列を評価してからメソッドを起動するとうまくいきました。

に高橋さんが実装したものを見つけました。"素朴な実装" のところが普通に移植したものです。

  • eval ではなく Object.const_get をつかっている。const_get って何でしょう。
  • ファイルを factory.rb / listfactory.rb / tablefactory.rb に分割している
  • raise NotImplementedError を記述してる箇所がある

というところ以外はほとんど一緒の実装でした。その他に

  • Constant メソッド
  • パーツカタログ
  • クラスインスタンス変数によるパーツカタログ
  • 命名の規約によるファクトリ

というものがありました。参考になります。いまから熟読します。