Murayama blog.

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

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パターンのお話でした。

ルクエのスチームケース

最近、「村山さんのブログつまんない」という声をよく聞くので、
たまには小ネタも挟んでいきたいと思います。
「クレームを大事にするのが牛角が躍進する要因だ」、とテレビで言ってたし。



さて、本日のテーマはこちら。

Lekue (ルクエ)【日本正規品】スチームケース トマト 62036

Lekue (ルクエ)【日本正規品】スチームケース トマト 62036



暇だったので雑誌を眺めていたら出会ってしまいました。
要は蒸し料理を電子レンジで簡単にできるってやつなんだけど、
「なにこれ、ヘルシーっぽいんじゃね?」
そんなことをTwitterでつぶやいてたのが一昨日の話。

男に必要なのは行動力である。

そんな言葉をモットーにしている僕は、
昨日、早速天王寺のロフトでルクエを購入!しかも今なら10%OFF。
買うしかない。買わねば失礼だ。


で、買っちゃいました。割引で4500円くらい?

男はデパ地下で食材を買うものである。

心斎橋の大丸で野菜とお魚をけっこう良い値段で購入。
金に糸目はつけない。
僕はなんてダメ人間なんだ。


そんで、早速調理開始。
野菜とお魚を適当に切ってレンジで5分。


できたみたい。
IMG_0204.JPG
ちなみに部屋が汚いとか気にしない。
あと、ランチョンマットも買ってみた。マイナス600円。
写真で見ると、箸置きを買わなかったのが悔やまれる。
ドンマイ俺。



で、開けてみた。
IMG_0206.JPG
画像はイケテナイですが、
スゲーいいかんじにできあがってました。
美味しさのあまり、食べ終わって残りのお野菜をもう一度チンしたくらいです。




以上、
人は時間を持て余すとダメになる。というお話でした。

実行スクリプトの探索パス

rubyの実行コマンドのオプションについて勉強しました。地味にまとめます。


参考書籍です。この本読んでRubyを勉強します。

Ruby逆引きハンドブック

Ruby逆引きハンドブック

-Sオプション

-Sオプションをつけた場合、実行スクリプトの探索パスから実行スクリプトを検索します。
実行スクリプトの検索パスは、
環境変数RUBYPATHと、環境変数PATHに指定されたディレクトリが対象になります。


例えば、以下のディレクトリ構成になっていて、
カレントディレクトリがrubyprjだとします。


rubyprj
  |-- dir1
  |  `-- hello.rb
  `-- dir2

※hello.rbはHello world!を出力するだけのスクリプトです。


以下のようにコマンドを実行すると、カレントディレクトリ(rubyprj)に実行スクリプトが存在しないためエラーになります。

rubyprj% ruby hello.rb
ruby: No such file or directory -- hello.rb (LoadError)


-Sオプションを指定せずに、相対パスでディレクトリを指定するならこんなかんじでもOKです。

rubyprj% ruby dir1/hello.rb
Hello world!


次に、-Sオプションによる実行スクリプトの検索パス指定を試してみます。
環境変数RUBYPATHにdir1を設定します。

RUBYPATH=/Users/xxx/rubyprj/dir1
export RUBYPATH


さきほどエラーになったコマンドを再度実行してみます。

rubyprj% ruby hello.rb
Hello world!


上手く動作しました。
環境変数RUBYPATHを使いましたが、環境変数PATHでも同様に動作します。
ただし、RUBYPATHに指定されたものが優先されます。

Strutsフレームワークについて

いろいろと思案した結果、第1回はStrutsのお話をしたいと思います。
それではどーぞ。


JavaでWebアプリケーションを構築するには、JSPServletといった技術がベースになります。
これらの技術はWebアプリケーションを構築する上で必須となる技術なのですが、
実際の開発現場では、JSPServletを全て一から実装することは少ないと思います。
JSPServletを全て一から開発すると、類似したコードがたくさん生まれ、生産効率が落ちるからです。
そのため、開発現場ではJSPServletを一から記述する代わりにフレームワークを利用することが多いです。
JavaでWebアプリケーションを構築するフレームワークとして最も有名なのがStrutsです。


Strutsを現場で使う、使わないは別として、Strutsは勉強しておいて損はないと思います。
Strutsは歴史あるフレームワークであるため、現存する様々なフレームワークのルーツになっています。*1


なので興味のある人は、Strutsでショッピングサイトくらいを試しに挑戦してみると良いかもしれません。
余談ですが、ショッピングサイトの構築というのは、ユーザ認証やセッション管理といった一般的なWebアプリケーションで利用される機能が必要になるので、勉強するのにちょうど良いテーマだと思います。
もちろんクレジット決済機能とかそういうのは遊びではできないけど、
DBのERを考えたりするのにもちょうど良いテーマだと思います。*2



あと、Strutsは書籍も豊富です。
本屋さんで何か1冊購入して勉強してみると良いかもしれません。
とりあえず、アマゾンのリンクを載せておきます。
Amazon.co.jp: struts: 本


ただ、注意しないといけないのは、Strutsはバージョン1.xと2.x系で仕組みが異なります。
これからは2.x系が主流になる?んでしょうけど、
個人的には2.x系はまだまだ情報が出揃っていない印象があります。
まわり道かもしれませんが、1.x系の仕組みを学んでから2.x系の勉強をした方が良いように思います。


また、Struts以外にも有名なフレームワークはたくさんあります。例えば、JSFとか。
以下は、Javaの開発現場でよく使われるフレームワークです。
今の時点では、とりあえず名前くらいは押さえておいて、余裕が出てきたら少しずつ勉強していくと良いと思います。


Javaフレームワークはいろいろ種類があるので、全部覚えるのは大変!と思うかもしれませんが、
いくつか勉強してみると、どれも仕組みはある程度似ていることに気づくと思います。
フレームワークというものに早めに慣れておくのも大事だと思います。


じゃー、第1回はこんなもんで。

*1:正確には、Strutsのダメなところを改善したフレームワークが多いです。

*2:あるいはミサ×もいいかもね。研修ネタ自重。

お疲れ様でした。

東京での新入社員研修が終わりました。
おかげさまで、なかなか濃い(楽しい)2ヶ月を過ごすことができました。


大阪に帰ってきた僕は少し時間ができたので、
研修で話せなかったことや、気づいたこと、その他もろもろをこれからブログでまとめていこうと思います。
不定期連載。

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:これも知らなかった。