特異メソッド
特定のインスタンスだけが実行できるメソッドを特異メソッド、というそうです。特異メソッド以外にも 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
endStrongDog#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 を記述してる箇所がある
というところ以外はほとんど一緒の実装でした。その他に
というものがありました。参考になります。いまから熟読します。