Decorator
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日は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
提供は、
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日は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
- メディア: ホーム&キッチン
- 購入: 37人 クリック: 2,395回
- この商品を含むブログ (59件) を見る
暇だったので雑誌を眺めていたら出会ってしまいました。
要は蒸し料理を電子レンジで簡単にできるってやつなんだけど、
「なにこれ、ヘルシーっぽいんじゃね?」
そんなことをTwitterでつぶやいてたのが一昨日の話。
男に必要なのは行動力である。
そんな言葉をモットーにしている僕は、
昨日、早速天王寺のロフトでルクエを購入!しかも今なら10%OFF。
買うしかない。買わねば失礼だ。
で、買っちゃいました。割引で4500円くらい?
男はデパ地下で食材を買うものである。
心斎橋の大丸で野菜とお魚をけっこう良い値段で購入。
金に糸目はつけない。
僕はなんてダメ人間なんだ。
そんで、早速調理開始。
野菜とお魚を適当に切ってレンジで5分。
できたみたい。
ちなみに部屋が汚いとか気にしない。
あと、ランチョンマットも買ってみた。マイナス600円。
写真で見ると、箸置きを買わなかったのが悔やまれる。
ドンマイ俺。
で、開けてみた。
画像はイケテナイですが、
スゲーいいかんじにできあがってました。
美味しさのあまり、食べ終わって残りのお野菜をもう一度チンしたくらいです。
以上、
人は時間を持て余すとダメになる。というお話でした。
実行スクリプトの探索パス
rubyの実行コマンドのオプションについて勉強しました。地味にまとめます。
参考書籍です。この本読んでRubyを勉強します。
- 作者: るびきち
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2009/05/25
- メディア: 単行本
- 購入: 23人 クリック: 255回
- この商品を含むブログ (60件) を見る
-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アプリケーションを構築するには、JSPやServletといった技術がベースになります。
これらの技術はWebアプリケーションを構築する上で必須となる技術なのですが、
実際の開発現場では、JSPやServletを全て一から実装することは少ないと思います。
JSPやServletを全て一から開発すると、類似したコードがたくさん生まれ、生産効率が落ちるからです。
そのため、開発現場ではJSPやServletを一から記述する代わりにフレームワークを利用することが多いです。
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回はこんなもんで。
Proxy
Rubyによるデザインパターン生活。続けます。
本日はProxyパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
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パターンの勉強でした。おしまい。