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 に定義されたメソッドのようです。

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 には他にも追加されているのかもしれません。

Rubyデスクトップリファレンス

Rubyデスクトップリファレンス

String#each

先の RSSJavaScript に変換するスクリプトでは

  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 に対してはどちらでも出力は同じなようです。)