Murayama blog.

プログラミング教育なブログ

Factory

Rubyによるデザインパターンの勉強です。残りわずかになってきました。
提供は、

Rubyによるデザインパターン

Rubyによるデザインパターン


本日はFactoryパターンを勉強します。
前回のSingletonと同様、Factoryパターンも有名なデザインパターンです。
GoFデザインパターンでは、
Factory Methodパターン、Abstract Factoryパターンに分類しています。
どちらもオブジェクトの生成を抽象化するデザインパターンになります。


ソースコードを解説する前に、本に載っているGoFデザインパターンの原則を引用しておきます。

  • デザインパターンの原則
    • 変わるものを変わらないものから分離する
    • インタフェースに対してプログラムし、実装に対して行わない
    • 継承より集約
    • 委譲、委譲、委譲


Factoryパターンは原則の一つ目がポイントになります。

  • 変わるものを変わらないものから分離する


それでは、本に載っているとおり、
「池でたたずんでいるアヒルの一日?」をイメージしたプログラムをFactoryパターンを見ていきます。


まずはアヒルを表すDuckクラスとDuckが存在する池となるPondクラスを作ってみます。

class Duck
  def initialize(name)
    @name = name
  end
  
  def speak
    puts("Duck #{@name} speak.")
  end
end

class Pond
  def initialize(number_ducks)
    @ducks = []
    number_ducks.times do |i|
      duck = Duck.new("Duck#{i}")
      @ducks << duck
    end
  end
  
  def simulate_one_day
    @ducks.each { |duck| duck.speak}
  end
end

Duckクラスには鳴く(speak)メソッドが定義されています。
Pondクラスはコンストラクタで、池で泳ぐアヒルの数を受け取ります。
また、ある日のシミュレートを行うsimulate_one_dayメソッドも定義しています。


それでは上記のクラスを実行する呼び出しもとプログラムを作成します。

Pond.new(3).simulate_one_day

3匹のアヒルが池で泳ぐ様子をシミュレートします。
実行結果は以下のようになります。

Duck Duck0 speak.
Duck Duck1 speak.
Duck Duck2 speak.


ここまではなんてことのないプログラムですね。
ここから、Factoryパターンの勉強になります。


ここで、仕様変更?が発生し、
さきほどのアヒル(Duck)クラスを、カエル(Frog)クラスに変更する必要があるとします。
カエル(Frog)クラスは以下のようになります。

class Frog
  def initialize(name)
    @name = name
  end

  def speak
    puts("Frog #{@name} speak.")
  end
end

カエル(Frog)クラスは、さきほどのアヒル(Duck)クラスと同じspeakメソッドを実装しています。
つまり、同じインタフェースで操作できるとも言えます。(Rubyの場合、ダックタイピングで操作できます)


問題は、Pondクラスをどのように変更するのか?というところです。
もう一度Pondクラスを見てみます。

class Pond
  def initialize(number_ducks)
    @ducks = []
    number_ducks.times do |i|
      duck = Duck.new("Duck#{i}")
      @ducks << duck
    end
  end
  
  def simulate_one_day
    @ducks.each { |duck| duck.speak}
  end
end

ここでDuckクラスに依存する部分に着目すると、

      duck = Duck.new("Duck#{i}")

の部分がDuckクラスに依存しているとわかります。
インスタンス変数の名前や、引数の名前がduckを含んでいる点は別途修正します。


ここで、冒頭に述べたデザインパターンの原則である

  • 変わるものを変わらないものから分離する

がポイントになります。


さきほどのDuckオブジェクトを生成している1行をメソッドとして切り出し、サブクラスに実装を任せることにします。
#あわせて変数名も修正します。

class Pond
  def initialize(number_animals)
    @animals = []
    number_animals.times do |i|
      animal = new_animal("Animal#{i}")
      @animals << animal
    end
  end
  
  def simulate_one_day
    @animals.each { |animal| animal.speak}
  end
end

class DuckPond < Pond
  def new_animal(name)
    Duck.new(name)
  end
end

class FrogPond < Pond
  def new_animal(name)
    Frog.new(name)
  end
end

PondクラスではDuck、Frogオブジェクトを生成する処理を抽象化しています。
実際のオブジェクト生成する処理はサブクラスであるDuckPondクラス、FrogPondクラスに任せています。
Gofは、クラスの選択をサブクラスに任せることをFactory Methodパターンと呼んでいます。


Factory Methodパターンをクラス図で表現すると以下のようになります。

Creator

    • Productオブジェクトを生成するクラス。実際にオブジェクトを生成するかはサブクラスに任せる。Pondクラスが該当する。

ConcreteCreator1、2

    • 特定のProductオブジェクトを生成するクラス。DuckPondクラス、FrogPondクラスが該当する。

Product

    • Creatorクラス(サブクラスも含む)によって生成されるクラス。Javaの場合はスーパークラスやインタフェースとなるが、Rubyの場合は不要。

Product1、2

    • Creatorクラスによって生成される具象クラス。Duckクラス、Frogクラスが該当する。


ここからAbstract Factoryパターンまで、もうちょっとだけ続きます。


つぎに、動物(Duck、Frog)以外にも植物を追加します。
ここでは、藻(Algae)、スイレン(WaterLily)クラスを作成します。

class Algae
  def initialize(name)
    @name = name
  end
  
  def grow
    puts "Algae #{@name} grow."
  end
end

class WaterLily
  def initialize(name)
    @name = name
  end
  
  def grow
    puts "WaterLily #{@name} grow."
  end
end

2つのクラスには植物の成長を示すgrowメソッドが定義されています。
これらのクラスのオブジェクトも生成できるようにPondクラスを変更します。

class Pond
  def initialize(number_animals, number_plants)
    @animals = []
    number_animals.times do |i|
      animal = new_animal("Animal#{i}")
      @animals << animal
    end
    
    @plants = []
    number_plants.times do |i|
      plant = new_plant("Plant#{i}")
      @plants << plant
    end
    
  end
  
  def simulate_one_day
    @plants.each { |plant| plant.grow}
    @animals.each { |animal| animal.speak}
  end
end

コンストラクタには、Algaeオブジェクト、WaterLilyオブジェクトを返すnew_plantメソッドを追加しています。
同様にsimulate_one_dayメソッドでも@plants変数にアクセスしています。


実際にAlgaeオブジェクトや、WaterLilyオブジェクトを作成するクラスは次のようになります。
#今回はDuckとWaterLily、FrogとAlgaeの組み合わせのみ有効とします。

class DuckWaterLilyPond < Pond
  def new_animal(name)
    Duck.new(name)
  end
  
  def new_plant(name)
    WaterLily.new(name)
  end
end

class FrogAlgaePond < Pond
  def new_animal(name)
    Frog.new(name)
  end
  
  def new_plant(name)
    Algae.new(name)
  end
end

ここまでくると、Abstract Factoryパターンと呼んでも良いと思います。
Abstract Factoryパターンは、矛盾のないオブジェクトの組み合わせを実現します。
このプログラムでは、DuckとWaterLily、FrogとAlgaeの組み合わせのみを許可しています。


最後にAbstarct Factoryパターンのクラス図です。

  • AbstractFactory
    • 複数のオブジェクト生成処理の組み合わせを定義するクラス(インタフェース)。Pondクラスが該当する。
  • ConcreteFactoryA、B
    • 特定のオブジェクトの組み合わせを実装するクラス。DuckWaterLilyPond、FrogAlgaePondクラスが該当する。
  • ProductA1、A2、B1、B2
    • CocreteFactoryA、Bによって生成されるクラス。DuckクラスWaterLilyクラスなどが該当する。


本では、ここから先もいろんな変更を加えていくんだけど、
長くなるのでFactoryの話題はこのへんで終わります。


最後にクラスオブジェクトを渡すFactoryについてもコードだけメモ。

class Pond
  def initialize(number_animals, animal_class, number_plants, plant_class)
    @animals = []
    number_animals.times do |i|
      animal = animal_class.new("Animal#{i}")
      @animals << animal
    end
    
    @plants = []
    number_plants.times do |i|
      plant = plant_class.new("Plant#{i}")
      @plants << plant
    end
  end
  
  def simulate_one_day
    @plants.each { |plant| plant.grow}
    @animals.each { |animal| animal.speak}
  end
end

呼び出しもとプログラムは以下のとおり。

Pond.new(3, Duck, 2, Algae).simulate_one_day

こっちの方がシンプルでRubyっぽくていいな。#Javaでもできるけど。

Decorator

Rubyによるデザインパターンの勉強です。
提供は、

Rubyによるデザインパターン

Rubyによるデザインパターン



本日はSingletonパターンを勉強します。


デザインパターンと言えばSingletonかっていうくらい有名なパターンです。
Singletonとは「オブジェクトを1つしか作らない」ことを指します。


そういえば、僕が研修のとき、
初めて教えてもらったデザインパターンはSingletonパターンだったような気がする。


今回も本に載っているとおり、
Logger(ログを出力する)クラスを作ってSingletonパターンを実装してみます。


まずはログを出力するSimpleLoggerクラスを作成します。
SimpleLoggerクラスにはerror、warning、infoの3つのメソッドが存在します。
それぞれのメソッドはログの出力レベル(@level)によって出力有無を切り替えます。

class SimpleLogger
  
  attr_accessor :level
  
  ERROR = 1
  WARNING = 2
  INFO = 3
  
  def initialize
    @log = File.open("log.txt", "w")
    @level = WARNING
  end
  
  def error(msg)
    @log.puts(msg)
    @log.flush
  end

  def warning(msg)
    @log.puts(msg) if @level >= WARNING
    @log.flush
  end

  def info(msg)
    @log.puts(msg) if @level >= INFO
    @log.flush
  end
end

上記のSimpleLoggerクラスはまだSingletonになっていません。
なので通常とおりオブジェクトを生成して利用します。
呼び出しもとプログラムは以下のとおりです。

logger = SimpleLogger.new
logger.level = SimpleLogger::INFO

logger.info("info1")
logger.warning("warning2")
logger.error("error3")

ログファイル(log.txt)には以下のように出力されます。

info1
warning2
error3


上記のコードだと、newメソッドを呼び出すたびに新しいオブジェクトを生成することができます。
(つまり、Singletonではない、ということになります)


それでは、SimpleLoggerクラスを修正してSingletonにしてみます。
まずはSingletonとなるオブジェクトを保持するクラス変数を追加します。
次に、クラス変数に追加したオブジェクトを返すクラスメソッドを追加します。

class SimpleLogger
  
  attr_accessor :level
  
  ERROR = 1
  WARNING = 2
  INFO = 3
  
  def initialize
    @log = File.open("log.txt", "w")
    @level = WARNING
  end
  
  def error(msg)
    @log.puts(msg)
    @log.flush
  end

  def warning(msg)
    @log.puts(msg) if @level >= WARNING
    @log.flush
  end

  def info(msg)
    @log.puts(msg) if @level >= INFO
    @log.flush
  end

  @@instance = SimpleLogger.new
  
  def self.instance
    @@instance
  end
end

追加したのはコードの最後の部分です。

  @@instance = SimpleLogger.new
  
  def self.instance
    @@instance
  end

この変更により、呼び出しもとでは、newメソッドの呼び出しからinstanceメソッドの呼び出しに変更することでSingletonなオブジェクトを取得することができます。

logger = SimpleLogger.instance
logger.level = SimpleLogger::INFO

logger.info("info1")
logger.warning("warning2")
logger.error("error3")


もう少し修正してみます。
今のコードでは相変わらずnewメソッドを呼び出すことができてしまいます。
そこで、newメソッドの可視性をprivateに変更します。

class SimpleLogger
  
  attr_accessor :level
  
  ERROR = 1
  WARNING = 2
  INFO = 3
  
  def initialize
    @log = File.open("log.txt", "w")
    @level = WARNING
  end
  
  def error(msg)
    @log.puts(msg)
    @log.flush
  end

  def warning(msg)
    @log.puts(msg) if @level >= WARNING
    @log.flush
  end

  def info(msg)
    @log.puts(msg) if @level >= INFO
    @log.flush
  end

  @@instance = SimpleLogger.new
  
  def self.instance
    @@instance
  end
  
  private_class_method :new
end

newメソッドをprivateにするために最後の1行を追加しました。
Rubyには、とってつけたようなメソッドが用意されてるのがスゴいと思う。ちなみにインスタンスメソッドをprivateにする場合は、privateメソッドを利用するみたい。

  private_class_method :new

これで直接newメソッドを呼び出すことを禁止できました。


以上がクラスベースでSingletonパターンを実現する流れです。


ここからはRubyらしくSingletonパターンを実現するにはどうするか、ってお話です。


というか、、
Singletonパターンを実現するためにRubyにはSingletonモジュールが用意されています。
#なんでもアリな気がしてきました。。
Singletonモジュールを使って、さきほどのSimpleLoggerクラスを修正してみます。
requireと、includeを追加するだけです。

require "singleton"
class SimpleLogger
  include Singleton
  
  attr_accessor :level
  
  ERROR = 1
  WARNING = 2
  INFO = 3
  
  def initialize
    @log = File.open("log.txt", "w")
    @level = WARNING
  end
  
  def error(msg)
    @log.puts(msg)
    @log.flush
  end

  def warning(msg)
    @log.puts(msg) if @level >= WARNING
    @log.flush
  end

  def info(msg)
    @log.puts(msg) if @level >= INFO
    @log.flush
  end
end

Singletonモジュールをincludeすると、instanceメソッドが定義され、newメソッドがprivateに設定されます。


以上、おしまい。

Decorator

Rubyによるデザインパ(ry
提供は、

Rubyによるデザインパターン

Rubyによるデザインパターン


本日はDecoratorパターンを勉強します。


Decoratorパターンは、既存のオブジェクトに対しての機能追加を実現するパターンです。
decorateは、装飾する、という意味になります。
オブジェクトに機能という名の装飾を施していくパターンかな、と。


上の最後の1行、スベってる感があるな。今日は不調かも。


JavaのDecoratorパターンといえば、
BuffererdReaderといったIOのAPIが思い浮かびます。
BuffererdReaderの場合は、ファイル読み込みというベース機能に、バッファリング機能を装飾してると考えることができます。


今回は本に載っているとおり、
ファイル出力機能をDecoratorパターンで実装してみます。
ただし、ファイルを出力する際に、必要に応じて以下の機能を追加できるようにします。

  • 行番号の表示
  • タイムスタンプの表示


まずはファイル出力を行うSimpleWriteクラスを作成します。
名前のとおり、シンプルにファイル出力のみを行うクラスです。

class SimpleWriter
  def initialize(path)
    @file = File.open(path, "w")
  end
  
  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end
  
  def pos
    @file.pos
  end
  
  def rewind
    @file.rewind
  end
  
  def close
    @file.close
  end
end

データを出力するwrite_lineメソッド、ファイル出力ポジションをコントロールするposメソッド、rewindメソッド、ファイル出力を閉じるcloseメソッドを実装しています。


SimpleWriteクラスを呼び出すプログラムは以下のようになります。

f = SimpleWriter.new("file1.txt")
f.write_line("Hello world.")
f.close

結果として、file1.txtファイルにHello world.が出力されます。


上記のSimpleWriterクラスに、行番号出力機能を装飾するNumberingWriterクラスを作成します。
NumberingWriterクラスは以下のとおりです。

class NumberingWriter < WriterDecorator

  def initialize(real_writer)
    super(real_writer)
    @line_number = 1
  end
  
  def write_line(line)
    @real_writer.write_line("#{@line_number} : #{line}")
  end
end

NumberingWriterクラスは、
コンストラクタの引数で受け取ったオブジェクトをインスタンス変数に保持します。
write_lineメソッドでは、行番号出力を施したあと、
コンストラクタで受け取ったオブジェクトに処理を委譲します。


つづいて、タイムスタンプ出力機能を装飾するTimestampoingWriterクラスです。

class TimestampingWriter < WriterDecorator
  def write_line(line)
    @real_writer.write_line("#{Time.new} : #{line}")
  end
end


また、NumberingWriterクラス、TimestampingWriterクラスはともにWriteDecoratorクラスを継承しています。
WriteDecoratorクラスは、NumberWriterクラス、TimestampingWriterクラスの共通機能を切り出したクラスになります。
話が前後しますが、このクラスでインスタンス変数real_writerを定義しているのが、Decoratorパターンのポイントになります。

class WriterDecorator
  def initialize(real_writer)
    @real_writer = real_writer
  end
  
  def write_line(line)
    @real_writer.write_line(line)
  end
  
  def pos
    @real_writer.pos
  end
  
  def rewind
    @real_writer.rewind
  end
  
  def close
    @real_writer.close
  end
end


では、呼び出しもとプログラムを作成してみます。

f = NumberingWriter.new(SimpleWriter.new("file1.txt"))
f.write_line("Hello world.")
f.close


ファイルに出力された結果は以下のとおりです。行番号が付加されます。

1 : Hello world.


同様にタイムスタンプの出力は、

f = TimestampingWriter.new(SimpleWriter.new("file1.txt"))
f.write_line("Hello world.")
f.close


ファイルに出力された結果は以下のとおりです。タイムスタンプが付加されます。

Fri Jul 03 17:57:24 +0900 2009 : Hello world.


で、行番号とタイムスタンプの両方の出力を追加してみます。

f = TimestampingWriter.new(NumberingWriter.new(SimpleWriter.new("file1.txt")))
f.write_line("Hello world.")
f.close


ファイルに出力された結果は以下のとおりです。行番号とタイムスタンプが付加されます。

1 : Fri Jul 03 17:58:56 +0900 2009 : Hello world.


以上がDecoratorパターンのサンプルになります。


Decoratorパターンのクラス図を見てみます。

  • Component
  • ConcreteComponent
    • ベースとなる処理をもつクラス。SimpleWriterクラスが該当する。
  • Decorator
    • ベースとなる処理に機能を装飾するクラス。NumberingWriterクラス、TimestampingWriterクラスが該当する。


ここから、RubyらしくDecoratorパターンをイジってみます。
NumberingWriterクラス、TimestampingWriterクラスの基底クラスであるWriterDecoratorクラスはForwardableモジュールを使用するとシンプルになります。

require "forwardable"
class WriterDecorator 
  extend Forwardable
  
  def_delegators :@real_writer, :write_line, :pos, :rewind, :close

  def initialize(real_writer)
    @real_writer = real_writer
  end
end

もともと、WriterDocoratorクラスは、real_writerオブジェクトへ処理を委譲していました。
処理の委譲は、forwardableモジュールのdef_delegatorsメソッドで行うことができます。
#ということを初めて知りました。勉強して良かった。
method_missingメソッドでも似たようなことがでますが、forwardableの方がわかりやすいです。
#method_missingは委譲するメソッドの数が多くなった場合に便利と本には書いています。このへんは上手く使い分けると良いですね。


まだまだあります。
次は実行時にオブジェクトを拡張する方法を見てみます。

f = SimpleWriter.new("file1.txt")
class << f
  alias old_write_line write_line
  
  def write_line(line)
    old_write_line("#{Time.new} : #{line}")
  end
end

面白いのはaliasメソッドで、既存のwrite_lineメソッドにエイリアス(old_write_line)を付けているところですね。
このあと、write_lineメソッドは再定義されて上書きされてしまうのですが、
old_write_lineメソッドは上書きされる前のメソッドを参照したままになります。
#これはスゴい。Rubyのスゴさがまた一つわかった気がする。。


あとは、モジュールを使って機能を追加する方法も紹介しています。

module NumberingWriter
  attr_reader :line_number
  
  def write_line(line)
    @line_number = 1 unless @line_number
    super("#{@line_number} : #{line}")
    @line_number += 1
  end
end

module TimestampingWriter
  def write_line(line)
    super("#{Time.new} : #{line}")
  end
end


NumberingWriter、TimestampingWriterをモジュールとして定義すると、
呼び出しもとプログラムは以下のようになります。

f = SimpleWriter.new("file1.txt")
f.extend(NumberingWriter)
f.extend(TimestampingWriter)
f.write_line("Hello world3.")
f.close


実行時にモジュールを追加する場合はextendメソッドを使います。
#このへんの感覚がRubyは難しいな。まだ慣れない。


委譲、じゃなかった、
以上、Decoratorパターンのお話でした。

Proxy

Rubyによるデザインパターン生活。続けます。
本日はProxyパターンを取り上げます。


Rubyによるデザインパターン

Rubyによるデザインパターン


Proxyパターンでは、呼び出し対象となるオブジェクトに対して、
同じインタフェースを持つ代理オブジェクトを用意します。
対象となるオブジェクトのメソッドをそのまま呼び出すのではなく、
代理オブジェクトを通じて、対象となるオブジェクトのメソッドを操作します。


Proxyパターンを用いることで、いわゆる「関心事の分離」を実現することができます。
関心事の分離とは、クラス(オブジェクト)の持つ本来の責務とは質の異なる要件(例えばセキュリティ要件やトランザクション管理など)を本来のクラスから切り離して実装することです。
#なんだか、ちょっとゴリ押しでまとめた感があるけど。。


この本では、Proxyパターンの活用方法を以下の3つに分類しています。

  • 防御Proxy
  • リモートProxy
  • 仮想Proxy


まずは防御Proxyから見ていきます。


この章のサンプルでは銀行処理クラス(BankAccount)クラスが登場します。

class BankAccount
  attr_reader :balance
  def initialize(balance)
    @balance = balance
  end
  
  def deposit(amount)
    @balance += amount
  end
  
  def withdraw(amount)
    @balance -= amount
  end
end

BankAccountクラスは残高照会処理(balance)と、入金処理(deposit)、引落処理(withdrow)を持っています。
言い換えると、BankAccountクラスは、銀行処理の基盤となる振る舞い(メソッド)は実装していますが、
利用権限を確認するためのユーザの認証処理といったセキュリティに関する要件は実装していません。


ここで、BankAccountクラスにユーザ認証処理を付加してみようと思います。
BankAccountクラスをそのまま修正してもよいのですが、その分、BankAccountクラスは複雑になってしまいます。
これはBankAccountクラスの本来の責務である銀行処理に加えて、ユーザ認証といった関心事の異なる要件を実装してしまうために発生する問題です。


そこでProxyパターンを用いると既存のBankAccountクラスに変更を加えずにユーザ認証機能を実現することができます。
BankAccountの代理となるBankAccountProxyクラスを作成します。

require "etc"
class BankAccountProxy
  def initialize(real_object, owner_name)
    @real_object = real_object
    @owner_name = owner_name
  end
  
  def balance
    check_access
    @real_object.balance
  end
  
  def deposit(amount)
    check_access
    @real_object.deposit(amount)
  end
  
  def withdraw(amount)
    check_access
    @real_object.withdraw(amount)
  end
  
  def check_access
    if(Etc.getlogin != @owner_name)
      raise "Illegal access: #{@owner_name} cannot access account."
    end
  end
end

BankAccounrProxyクラスは、インスタンス変数にBankAccountオブジェクトを保持します。
また、BankAccountProxyクラスには、depositやwithdrowといったメソッドを定義しています。
depositメソッドやwithdrowメソッドは、
内部でcheck_accessメソッドを呼び出しユーザ認証を行っています。*1
ユーザ認証をパスした場合のみ、BankAccountオブジェクトに処理を委譲することになります。


プログラムの呼び出しもとは次のようになります。

account = BankAccount.new(100)
proxy = BankAccountProxy.new(account, "murayama")
puts proxy.deposit(50)
puts proxy.withdraw(10)

ユーザ認証をパスした場合の実行結果は以下のとおりです。

150
140

一方で、ユーザ認証に失敗した場合はRuntimeErrorが発生します。


以上が、防御Proxyのお話になります。
ユーザ認証を担当するBankAccountProxyクラスを作成することで、
セキュリティ要件を代理クラスに任せることができました。


つづいて、仮想Proxyのお話です。
仕組みはさきほどの防御Proxyとよく似ていますが、その利用目的が異なります。
仮想Proxyでは、複雑なオブジェクトの生成を遅延させることを目的とします。


VirtualAccountProxyは入金処理(deposit)、引落処理(withdrow)、残高照会処理(balance)が呼び出されるまで、
処理の実体となるBankAccountオブジェクトの生成を遅延します。

class VirtualAccountProxy
  def initialize(starting_balance)
    @starting_balance = starting_balance
  end
  
  def balance
    subject.balance
  end
  
  def deposit(amount)
    subject.deposit(amount)
  end
  
  def withdraw(amount)
    subject.withdraw(amount)
  end
  
  def subject
    @subject || (@subject = BankAccount.new(@starting_balance))
  end
end

このVirtualAccountProxyは、subjectメソッドを呼び出すことでBankAccountオブジェクトを生成します。
subjectメソッドはdepositメソッドやwithdrowメソッドが呼び出されたときに実行します。
言い換えると、depositメソッドや、withdrowメソッドが呼び出されるまで、
処理の本体となるBankAccountオブジェクトは生成されないことになります。


以上がProxyパターンのお話です。
#リモートProxyの話はおいときます。
せっかくなのでクラス図も載せておきます。


と、ここまでが一般的なProxyパターンのお話です。


ここからは、さきほどの防御Proxy(BankAccountProxy)を、
Rubyらしいコードに変換してみます。


少し話が変わりますが、Rubyには未定義のメソッド呼び出しが発生した場合に、
method_missingという名前のメソッドが呼び出されます。*2
このmethod_missingメソッドを利用することで、
さきほどのBankAccountProxyクラスを次のように実装することができます。

require "etc"
class BankAccountProxy
  def initialize(real_object, owner_name)
    @real_object = real_object
    @owner_name = owner_name
  end
  
  def method_missing(name, *args)
    check_access
    @real_object.send(name, *args)
  end
  
  def check_access
    if(Etc.getlogin != @owner_name)
      raise "Illegal access: #{@owner_name} cannot access account."
    end
  end
end

depositメソッドや、withdrowメソッドを定義する必要がなくなったのでシンプルになりました。
method_missingメソッドの内部でsendメソッドを使用することで、本体となるオブジェクトのメソッドを呼び出しています。
また、method_missingメソッドの第2匹数*argsは不定個の引数を配列に格納します。


以上、Proxyパターンの勉強でした。おしまい。

*1:RubyのEtcモジュールが便利だということを知りました。

*2:これも知らなかった。

Adapter

Rubyによるデザインパターン生活。若干飽きてきたけど続けます。
本日はAdapterパターンを取り上げます。


Rubyによるデザインパターン

Rubyによるデザインパターン


Adapterパターンは、インターフェースの不一致を埋めるためのパターンです。
ここでいうインターフェースの不一致というのは、
メソッドのネーミング、シグネチャの不一致のことを意味しています。


例えば、次のようなプログラムがあるとします。
画面に文字列を出力するだけのシンプルなものです。
#今回は本に載ってるプログラムをもろパクリではなく、ちょっとアレンジしています。

class Renderer
  def execute(text_object)
    text_object.print
  end
end

class TextObject
  def print
    puts "TextObject print!"
  end
end

このプログラムは、Rendererクラスのexecuteメソッドの中で、
引数に受け取ったオブジェクトのprintメソッドを呼び出します。
引数のオブジェクトには、TextObjectを渡すことができます。

renderer = Renderer.new
text_object = TextObject.new
renderer.execute(text_object)


実行結果は以下のとおりです。

TextObject print!


次に、Rendererクラスのexecuteメソッドの引数に与えるオブジェクトを、
さきほどのTextObjectからLegacyTextObjectに変更します。
LegacyTextObjectクラスは以下のとおりです。

class LegacyTextObject
  def show_message
    puts "LegacyTextObject show_message!"
  end
end


ここで問題が発生します。
LegacyTextObjectクラスにはprintメソッドが定義されていません。
その代わりに類似したメソッドであるshow_messageが定義されています。
このままの状態で、さきほどと同じようにプログラムを実行するとエラーが発生してしまいます。

renderer = Renderer.new
lto = LegacyTextObject.new
renderer.execute(lto)

実行結果(NoMethodError)

NoMethodError: private method ‘print’ called for #<LegacyTextObject:0x1ce6c>


エラーの原因はLegacyTextObjectクラスにprintメソッドが定義されていないためです。
一番簡単な解決法はLegacyTextObjectクラスのshow_messageメソッドを、
printメソッドに名前を変更することですが、
LegacyTextObjectクラスが既に別のプログラムから利用されている場合は、変更箇所が増えてしまいます。


上記のような問題の解決策となるのがAdapterパターンです。
Adapterパターンでは、インタフェースの不一致を吸収するためのAdapterクラスを作成します。

class LegacyTextObjectAdapter
  def initialize(legacty_text_object)
    @lto = legacty_text_object
  end
  
  def print
    @lto.show_message
  end
end


実行時には、LegacyTextObjectAdapterを使ってLegacyTextObjectをラップします。

renderer = Renderer.new
lto = LegacyTextObject.new
renderer.execute(LegacyTextObjectAdapter.new(lto))


すると上手く動作します。(実行結果です)

LegacyTextObject show_message!


Adapterパターンのクラス図は、以下のようになります。


各クラスの役割は以下のとおりです。

  • Client
    • Targetのメソッドを呼び出すクラス。
  • Target
    • 実行対象となるクラス(インタフェース)。
  • Adapter
    • Targetインタフェースと、Adapteeクラスの差分を埋めるクラス。
  • Adaptee
    • 実際に動作するクラス。


先ほどのプログラムの場合、以下のような構成になります。

  • Client
    • Rendererクラス。
  • Target
    • なし。Rubyの場合はインタフェース不要。*1
  • Adapter
    • LegacyTextObjectAdapterクラス。
  • Adaptee
    • LegacyTextObjectクラス。


以上がAdapterパターンの説明です。
なんですが、この本の面白いところはここからです。


Rubyは動的型付け言語です。
Javaのようにコンパイル時に変数の型をチェックするような仕組みはありません。
そのため、Adapterパターンにおいても、
Ruby特有の、動的型付け言語特有の解決策が存在します。


例えば、実行時にクラスを修正することで、メソッドを追加することができます。
LegacyTextObjectがロード済みの場合、以下のようにクラス自身を再定義することができます。

class LegacyTextObject
  def print
    show_message
  end
end


これにより実行時にprintメソッドが追加されます。
printメソッドの内部では、show_messageメソッドを呼んでいるので、
結果的にshow_messageメソッドが呼び出されることになります。


上記の方法はクラス定義自体に変更が発生します。
LegacyTextObjectクラスのすべてのオブジェクトはprintメソッドを保持することになります。


クラスに直接メソッドを追加するのではなく、
特定のオブジェクトにだけメソッドを追加することもできます。

lto = LegacyTextObject.new
class << lto
  def print
    show_message
  end
end

この方法ではlto変数に格納されているオブジェクトのみがprintメソッドを保持することになります。
Rubyではオブジェクトに固有なメソッドのことを特異メソッド(Singletonメソッド)と呼びます。
特異メソッドは、以下のように実装することもできます。

lto = LegacyTextObject.new
def lto.print
  show_message
end


以上で説明はおしまいです。
Adapterパターンは理解しやすいパターンだと思います。
ただ、僕の場合はこれまでJavaの考え方(静的型付け)で理解していました。
そのため、Rubyのような動的型付けの世界ではいろんな代替手段があるんだなー、と、
改めて感じました。
プログラミングってほんとうにおもしろいですね、みたいな。

*1:ダックタイピングというやつ

Command

Rubyによるデザインパターン生活。継続中。
本日はCommandパターンを取り上げます。


Rubyによるデザインパターン

Rubyによるデザインパターン


CommandパターンのCommand(コマンド)とは命令の意味です。
この本には、

コマンドはある特定の何かをするための命令です。

と定義されています。


あ、今回からクラス図をアップすることにしました。
それではCommandパターンのクラス図です。


Commandパターンはとてもシンプルな構成になっています。
上記のクラス図では、抽象的なCommandクラス(インタフェース)を実装する、
ConcreteCommand1、ConcreteCommand2クラスを示しています。


Commandインタフェースにはexecuteメソッドが定義されています。
このexecuteメソッドが「ある特定の何かをするための命令」になります。


今回のサンプルに登場するクラスです。

  • Command
    • Commandクラス。名前そのままでCommandパターンのCommandを担当します。
  • CreateFile
    • Commandのサブクラス。ファイルを作成するクラス。
  • DeleteFile
    • Commandのサブクラス。ファイルを削除するクラス。
  • CopyFile
    • Commandのサブクラス。ファイルをコピーするクラス。

※CreateFile、DeleteFile、CopyFileクラスは、ConcreteCommand1(2)クラスのように振る舞います。

  • CompositCommand
    • 複数のコマンドを集約するクラス。自身もCommandのサブクラスであり、保持する複数のコマンドをまとめて実行します。


つづいて、ソースコードです。

require "fileutils"

class Command
  attr_reader :description
  def initialize(description)
    @description = description
  end
  
  def execute
  end
end

class CreateFile < Command
  def initialize(path, contents)
    super("Create file : #{path}")
    @path = path
    @contents = contents
  end
  
  def execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
end


class DeleteFile < Command
  def initialize(path)
    super("Delete file : #{path}")
    @path = path
  end
  
  def execute
    File.delete(@path)
  end
end

class CopyFile < Command
  def initialize(source, target)
    super("Copy file : #{source} to #{target}")
    @source = source
    @target = target
  end
  
  def execute
    FileUtils.copy(@source, @target)
  end
end

class CompositCommand < Command
  def initialize
    @commands = []
  end
  
  def add_command(cmd)
    @commands << cmd
  end
  
  def execute
    @commands.each { |cmd| cmd.execute }
  end
  
  def description
    description = ""
    @commands.each { |cmd| description += cmd.description + "\n"}
    description
  end
end


cmds = CompositCommand.new
cmds.add_command(CreateFile.new("file1.txt", "hello world\n"))
cmds.add_command(CopyFile.new("file1.txt", "file2.txt"))
cmds.add_command(DeleteFile.new("file1.txt"))

cmds.execute

Commandクラスとそのサブクラスを定義したあと、CompositCommandオブジェクトを生成し、
CreateFile、CopyFile、DeleteFileオブジェクトを追加しています。
その後、追加したCommandオブジェクトを追加した順にまとめて実行しています。


実行結果は、カレントディレクトリに作成したfile1.txtがコピーされてfile2.txtとなります。
また、コピー元となったfile1.txtは削除されます。
結果としてfile2.txtのみが残ります。


また、さきほどのプログラムの最後で、

puts cmds.description

と実行すると、画面に集約したコマンドの一覧を出力します。

Create file : file1.txt
Copy file : file1.txt to file2.txt
Delete file : file1.txt


ここまでの流れをみると、
一つひとつのCommandの処理はexecuteメソッドを実装するだけで単純なものです。
「executeメソッドを実装した」から「Commandパターンだ」と思っていてはいけません。
本に載っている大事な部分を引用します。

Commandパターンのポイントは、何を行うかの決定と、それの実行を分離することです。
このパターンを使う場合、「これを行え」と命令する代わりに、「これを行う方法を記録しろ」と命令し、その後「記録したことを行え」と命令します。

Commandパターンは命令と実行の分離と考えると良いと思います。


また、Commandパターンの応用編として、
Undo(やりなおし)の実装も紹介されています。
さきほどのプログラムの各Commandクラスにunexecuteメソッドを追加してみます。

require "fileutils"

class Command
  attr_reader :description
  def initialize(description)
    @description = description
  end
  
  def execute
  end
end

class CreateFile < Command
  def initialize(path, contents)
    super("Create file : #{path}")
    @path = path
    @contents = contents
  end
  
  def execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
  
  def unexecute
    File.delete(@path)
  end
end

class DeleteFile < Command
  def initialize(path)
    super("Delete file : #{path}")
    @path = path
  end
  
  def execute
    if(File.exists?(@path))
      @contents = File.read(@path)
    end
    File.delete(@path)
  end
  
  def unexecute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
end

class CopyFile < Command
  def initialize(source, target)
    super("Copy file : #{source} to #{target}")
    @source = source
    @target = target
  end
  
  def execute
    if(File.exists?(@target))
      @contents = File.read(@target)
    end
    FileUtils.copy(@source, @target)
  end
  
  def unexecute
    File.delete(@target)
    if(@contents)
      f = File.open(@target, "w")
      f.write(@contents)
      f.close
    end
  end
end

class CompositCommand < Command
  def initialize
    @commands = []
  end
  
  def add_command(cmd)
    @commands << cmd
  end
  
  def execute
    @commands.each { |cmd| cmd.execute }
  end
  
  def unexecute
    @commands.reverse.each { |cmd| cmd.unexecute }
  end
  
  def description
    description = ""
    @commands.each { |cmd| description += cmd.description + "\n"}
    description
  end
end

cmds = CompositCommand.new

cmds.add_command(CreateFile.new("file1.txt", "hello world10\n"))
cmds.add_command(CopyFile.new("file1.txt", "file2.txt"))
cmds.add_command(DeleteFile.new("file1.txt"))

cmds.execute
cmds.unexecute

最後にcmds.unexecuteメソッドを呼び出すことで、
コピー処理の一連の流れを取り消すことができます。

Iterator

Rubyによるデザインパターン生活、何日目とかもういいや。
本日はIteratorパターンを取り上げます。


Rubyによるデザインパターン

Rubyによるデザインパターン


Iteratorパターンは、
「集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する」とGoFは定義しています。
Javaでよく出てくるIteratorインタフェースがまさにそれです。
Iteratorインタフェースを利用することでListオブジェクトなど内部を意識せず、順番に要素へアクセスすることができます。


RubyにおけるIteratorパターンでは、外部イテレータと内部イテレータが存在します。
外部イテレータってのは、JavaIteratorインタフェースのようなクラスを自分で作ってしまう方法で、
内部イテレータってのは、Rubyでよく見るeach{|element| #do something}のようなメソッドを意味します。
*1


で、まずは外部イテレータを試してみます。
配列オブジェクト用のイテレータ(ArrayIterator)クラス。

class ArrayIterator
  def initialize(array)
    @array = array
    @index = 0
  end
  
  def has_next?
    @index < @array.length
  end
  
  def item
    @array[@index]
  end
  
  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end


使い方はこんなかんじ。

array = ["red", "green", "blue"]
it = ArrayIterator.new(array)
while it.has_next? 
  puts "item: #{it.next_item}"
end


そんで実行結果。

item: red
item: green
item: blue


ちなみに名前はArrayIteratorだけど、配列オブジェクトにしか使えないわけではなくて、文字列オブジェクトの操作にも使えます。

it = ArrayIterator.new("test")
while it.has_next?
  puts "item:#{it.next_item.chr}"
end


文字列操作の場合は文字コードが返されるので、chrメソッドで文字データに変換しています。
実行結果は以下のとおり。

item:t
item:e
item:s
item:t


続いて、内部イテレータの話です。
Arrayオブジェクトに定義されているeachメソッドのようなものを独自で実装すると以下のようになります。

def for_each_element(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
end

引数で受け取ったArrayオブジェクトに対して、yieldメソッドでコードブロックを順に実行するかんじです。


呼び出し側のプログラムは以下のようになります。

a = [10, 20, 30]
for_each_element(a){ |element| puts element }


実行結果です。

10
20
30


外部イテレータより、内部イテレータの方がRubyっぽいというか便利なように思います。
ただし、2つのArrayオブジェクトをマージするような場合は、
外部イテレータを使う方が楽なようです。

def merge(array1, array2)
  merged = []
  
  it1 = ArrayIterator.new(array1)
  it2 = ArrayIterator.new(array2)
  
  while it1.has_next? && it2.has_next?
    if it1.item < it2.item
      merged << it1.next_item
    else
      merged << it2.next_item
    end
  end
  
  while it1.has_next?
      merged << it1.next_item
  end

  while it2.has_next?
      merged << it2.next_item
  end
  merged
end

a = [10, 20, 30]
b = [11, 12, 21, 22]
merge(a, b).each { |e|  
  puts e
}


マージの実行結果です。

10
11
12
20
21
22
30


Iteratorパターンのお話はこんなところなんだけど、
この章では、Enumerableモジュールの使い方を紹介しています。


Enumerableモジュールをインクルードすると、集約オブジェクトに対して便利なメソッドを取り込むことができます。
たとえば、all?とかany?とかinclude?とか。

Enumerableモジュールをインクルードするには、以下の点を考慮します。

  • 内部イテレータメソッドにeachという名前をつけること。
  • <=>比較演算子の適切な実装をもつこと。
class Account
  attr_accessor :name, :balance
  
  def initialize(name, balance)
    @name = name
    @balance = balance
  end
  
  def <=>(other)
    @balance <=> other.balance
  end
end


class Portfolio
  include Enumerable
  
  def initialize
    @accounts = []
  end
  
  def each(&block)
    @accounts.each(&block)
  end
  
  def add_account(account)
    @accounts << account
  end
end

account1 = Account.new("account1", 1000)
account2 = Account.new("account2", 5000)
account3 = Account.new("account3", 3000)
account4 = Account.new("account4", 4000)
portfolio = Portfolio.new
portfolio.add_account(account1)
portfolio.add_account(account2)
portfolio.add_account(account3)
portfolio.add_account(account4)

puts portfolio.any? { |account| account.balance > 2000 }
puts portfolio.all? { |account| account.balance > 2000 }


実行すると、trueが表示されます。

true
false


あと、この章の最後のところで、ObjectSpaceモジュールも紹介されています。

ObjectSpaceは、Rubyインタープリタの中に存在する完全なオブジェクト空間へのアクセスを提供します。

初めて知ったけど、これけっこう面白い。どこで使えばよいのかはわからないけど。
さっきのプログラムのつづきに以下のコードを追加すると、

ObjectSpace.each_object(Account){ |object| puts object.name }


こんなふうにメモリ上に存在するインスタンスを取得することができます。

account4
account3
account2
account1

以上。

*1:こういう話は言われてみたら、そうだなーって思うけど、言われなかったら意識しないままだと思う。