instance_eval?
eval と言えば、デスクトップリファレンスに instance_eval というメソッドが載っていました。
オブジェクトのコンテキストで eval する。オブジェクトのインスタンス変数などに直接アクセスできる。
とあるのですが、よくわかりません。あと、よくサンプルなどで "class" ではなく "klass" と書いてるものがありますが、あれはどういう意味合いがあるのでしょうか。
Builder パターン
id:hyuki さんのデザインパターン本から Prototype パターンを移植してみます。
#!/usr/local/bin/ruby class Director def initialize(builder) @builder = builder end def construct @builder.make_title("Greeting") @builder.make_string("朝から昼にかけて") @builder.make_items("おはようございます", "こんにちは") @builder.make_string("夜に") @builder.make_items("こんばんは", "おやすみなさい", "さようなら。") @builder.close end end class TextBuilder def initialize @text = String.new end def make_title(title) @text += "=========================================?n" @text += "『#{title}』?n?n" end def make_string(str) @text += "■ #{str}?n?n" end def make_items(*items) items.each do |item| @text += " ・#{item}?n" end end def close @text += "=========================================?n" end def result @text end end builder = TextBuilder.new Director.new(builder).construct puts builder.result
実行結果は、
========================================= 『Greeting』 ■ 朝から昼にかけて ・おはようございます ・こんにちは ■ 夜に ・こんばんは ・おやすみなさい ・さようなら。 =========================================
となりました。新しく実践したことと言えば make_items(*items) として引数を配列で受け取っているところぐらいです。
今回もインタフェースや抽象クラス抜きです。HTMLBuilder を追加するときはダックタイピングです。Ruby っぽくするアイデアは特に思いつきませんでした。
Prototype パターン
id:hyuki さんのデザインパターン本から Prototype パターンを移植してみます。
#!/usr/local/bin/ruby class Manager def initialize @showcase = Hash.new end def register (name, proto) @showcase[name] = proto end def create(protoname) p = @showcase[protoname] p.create_clone end end class MessageBox def initialize(decochar) @decochar = decochar end def use(str) length = str.length (length + 4).times { print @decochar } puts "" puts "#@decochar #{str} #@decochar" (length + 4).times { print @decochar } puts "" end def create_clone self.dup end end manager = Manager.new manager.register('warning box', MessageBox.new('*')) manager.register('slash box', MessageBox.new('/')) [manager.create('warning box'), manager.create('slash box')].each do |product| product.use("Hello, World!") end
このスクリプトの実行結果は、
$ ruby prototype.rb ***************** * Hello, World! * ***************** ///////////////// / Hello, World! / /////////////////
となります。
書籍では Product というインタフェースが出てきて、MessageBox はその Product を継承しています。Product が MessageBox のインタフェースを規定します。一方の移植版は、Ruby なのでインタフェースのような概念がないため、このような実装になりました。
書籍ではもう一つ Underline という、Product を継承したクラスがでてきます。Product インタフェースによって MessageBox と Underline はどちらも同じインタフェースを持つことになり、また Manager クラスはそれらサブクラスに束縛されることなく Product インタフェースを扱うことで、結合が疎になるという例。
Ruby の場合はこの辺はダックタイピングで、ということになるかと思います。
ところで MessageBox の create_clone は移植のために便宜的に用意してますが、Ruby は Kernel#clone や Kernel#dup があるためすべてのオブジェクトが複製可能です。従ってこのメソッドは削除してしまって、Manager の create で直接 p.dup
してしまっても良さそうです。
すると Manager は
class Manager def initialize @showcase = Hash.new end def register (name, proto) @showcase[name] = proto end def create(protoname) p = @showcase[protoname] p.dup end end
となるわけですが、なんとなく Hash を中途半端に抽象化しているだけのクラスになってしまいました。いっそ Manager は Hash を継承させてしまって、create を追加したものでも良いかもしれません。
あるいは、Hash と同じインタフェースであるが、値を取り出すときはインスタンスのコピーが取り出されるというクラスにしてみるのも面白いかなと思います。これを以下のように実装してみました。
class PrototypeHash include Enumerable def initialize @internal = Hash.new end def each @internal.each do |key, value| yield(key, value) end end def []=(name, proto) @showcase[name] = proto end def [](proto) p = @showcase[proto] p.dup end end class MessageBox ... end manager = PrototypeHash.new manager[:warning] = MessageBox.new('*') manager[:slash] = MessageBox.new('/') [manager[:warning], manager[:slash]].each do |product| product.use("Hello, World!") end
うまく動きました。こういう実装の方が Ruby らしいような気がしますが、どうでしょうか。
Object クラスと Kernel の関係
リファレンスマニュアルを見たところ Ruby の全オブジェクトの祖先クラスである Object には実は initialize だけが定義されていて、他にはメソッドがないようです。では、Object#class や Object#methods といったメソッドは一体何者でしょうか。
その正体は Kernel でした。Kernel はクラスではなくモジュールで、Object は Kernel を Mixin していました。従ってすべてのクラスは Kernel をインクルードしていることになります。class や methods は Kernel に定義されたメソッドのようです。
Object.methods
"Hello, World!".methods.each do |method| puts method end
で String のメソッド一覧が作れました。
Singleton パターン
Singleton パターンを実装してみました。
#!/usr/local/bin/ruby class Singleton private_class_method :new @@instance = nil def Singleton.instance @@instance = new unless @@instance @@instance end end object1 = Singleton.instance object2 = Singleton.instance p object1 p object2
- private_class_method で Singleton#new を実行禁止にする
- クラスメソッドの定義の仕方 = def Singleton.method ... end
の二点がポイントです。実行結果は
$ ruby singleton.rb #<Singleton:0x357d00> #<Singleton:0x357d00>
となりました。確かに object1 と object2 は同じインスタンスです。
ところで、Ruby には Singleton パターン用のモジュールがあります。モジュールを include するだけでそのクラスが Singleton になります。
#!/usr/local/bin/ruby require 'singleton' class World include Singleton end world1 = World.instance world2 = World.instance p world1 p world2
Singleton パターン以外のデザインパターン用ライブラリとしては
- Delegate パターン
- Observer パターン
用のモジュールが標準サポートされているようです。手元にある Ruby デスクトップリファレンスに掲載されていたのはこの三つです。Ruby デスクトップリファレンスは 1.6 向けなので、1.8 には他にも追加されているのかもしれません。
- 作者: まつもとゆきひろ
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2000/11
- メディア: 単行本
- 購入: 1人 クリック: 10回
- この商品を含むブログ (19件) を見る
String#each
先の RSS を JavaScript に変換するスクリプトでは
def to_js self.to_html.split("?n").collect { |line| line.gsub!(/'/, "?'") "document.writeln('#{line}');" }.join("?n") end
という処理を書いていましたが、String の API を眺めていたところ、String は Enumerable を Mixin していて each は行単位に String を分割してイテレートしてくれるようです。なので自分で split している上記コードは冗長でした。
def to_js self.to_html.collect { |line| line.chomp! line.gsub!(/'/, "?'") "document.writeln('#{line}');" } end
と、書き替えました。また join して結果を返していましたが、Array で返しておいて使う側でよしなに扱うようにしました。(puts に対してはどちらでも出力は同じなようです。)