デザインパターンにおけるDecoratorパターンを用いたRubyの例
概要
オブジェクト指向の開発に携わったことがある人なら、継承を使ってクラスやオブジェクトに追加の動作を実装することは、すべてのオブジェクト指向言語の基本機能である。既存のクラスにいくつかのメソッドがない場合、またはメソッドにさらに機能(魅力)を追加する必要がある場合、そのクラスから継承して新しいクラスを生成することがあります - これは余分なコードの上に構築されるものです。
既存のクラスを継承することで、親クラスのメソッドと合わせて子クラスに独自のメソッドを持たせることができます。しかし、そのようなメソッドは静的なものであり、いつどのように追加されるかはユーザー側でコントロールできません。すでに初期化されているオブジェクトの振る舞いを変えたい場合はどうすればよいのでしょうか?あるいは、多くのクラスの振る舞いを継承したい場合、代わりに何をすればいいのでしょうか?前者は実行時にしかできませんし、後者は当然可能ですが、たくさんの異なるクラスができることになりかねません - 恐ろしいことです。
問題点
クラスの中に余計なコードを書かないで、基本的な機能やめったに使わない機能を簡単に追加できるように、どのようにコードを整理しているのでしょうか?
解決方法
- オブジェクトにいくつかの責任や動作を動的に追加します。Decoratorパターンは、サブクラスを生成するよりも柔軟に機能を追加することができます。
- サブクラスを変更する際の柔軟な解決策を提供します。Decoratorパターンは、元のクラスファイルを変更することなく、継承を使用してオブジェクトの機能を動的に拡張します。これは、実際のオブジェクトを包むラッパーオブジェクト(別名:デコレーション)を作成することで行います。
- デコレーター・パターンは、一連のサブクラスに対して使用するとより効果的です。親クラスから派生した)サブクラスのファミリーがあり、サブクラスから独立して使用するための機能を追加する必要がある場合、コードの重複や具象サブクラスの増加を避けるためにデコレーターパターンを使用することができます。
適用性
Decoratorパターンを使用するのは以下のようなケースです。
- 他のオブジェクトに影響を与えることなく、動的かつ透過的な方法で、単一のオブジェクトに責任を追加します。
- 取り消すことができる責任を処理します。
- サブクラスを生成する方法で拡張ができない場合。一つの状況として、以下のような独立した拡張子が多数存在する場合がある。
それぞれの組み合わせに対応するために大量のサブクラスが生成され、サブクラスの数が爆発的に増えることになる。
もう一つのシナリオは、クラス定義が隠されている、またはクラス定義を使用してサブクラスを生成できないことです。
インスタンス
class SimpleWriter
def initialize(path)
@file = File.open(path,"w")
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
#characters
def pos
@file.pos
end
# It will point the file pointer to the beginning of the file
def rewind
@file.rewind
end
def close
@file.colse
end
end
sw = SimpleWriter.new("test.txt")
sw.write_line("hello")
puts sw.pos
puts sw.rewind
#Base class
class WriterDecorator
def initialize(real_writer)
@real_writer = real_writer
end
def write_line
@real_writer.write_line
end
def pos
@real_writer.pos
end
def rewind
@real_writer.rewind
end
def close
@real_writer.close
end
end
class NumberingWriter < WriterDecorator
attr :line_number
def initialize(real_writer)
super(real_writer)
@line_number = 1
end
#The actual call is to WriterDecorator's write_line method, but with the number (decorated) in front of the written content
#That's why NumberingWriter decorates WriterDecorator's interface wirte_line
#
def write_line(line)
@real_writer.write_line("#{@line_number}:#{line}")
@line_number += 1
end
end
sw = SimpleWriter.new("numbering_write.txt")
nw = NumberingWriter.new(sw)
nw.write_line("hello,world")
nw.write_line("hello,ruby")
puts nw.line_number
class CheckSummingWriter < WriterDecorator
attr_reader :check_num
def initialize(real_writer)
super(real_writer)
@check_num = 0
end
def write_line(line)
line.each_byte{|byte| @check_num += byte % 256}
@real_writer.write_line(line)
end
end
sw = SimpleWriter.new("check_num_writer.txt")
csw = CheckSummingWriter.new(sw)
csw.write_line("hello,world")
puts csw.check_num
class TimeStampingWriter < WriterDecorator
def initialize(real_writer)
super(real_writer)
end
def write_line(line)
@real_writer.write_line("#{Time.now}: #{line}")
end
end
#Reversed
#5. The actual call is to SimpleWriter's write_line method, which writes the contents to the file
sw = SimpleWriter.new("mix.txt")
#4. The actual call is to the NumberingWriter's write_line method, which prefixes the input data with a number
#4. The actual call is to NumberingWriter's write_line method, which prefixes the input data with a number, and then passes it to @real_writer, which is @real_witer as sw
nw = NumberingWriter.new(sw)
#3. the actual call is to the TimeStampingWriter write_line method, which prefixes the input data with a timestamp
#4. then pass it to @real_writer, where @real_witer is nw
tsw = TimeStampingWriter.new(nw)
#2. The actual call is to CheckSummingWriter's write_line method, which counts the number of bytes of input data
#2. The actual call is to the CheckSummingWriter write_line method, which counts the number of bytes of input data, and then passes it to @real_writer, which is @real_witer at this point
csw = CheckSummingWriter.new(tsw)
# 1. csw calls write_line
csw.write_line("hello,world")
puts csw.check_num
ルビー風装飾パターンアプリケーション2種
(1)モジュールを混ぜるにはextendを使う
class SimpleWriter
def initialize(path)
@file = File.open(path,"w")
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
#characters
def pos
@file.pos
end
# It will point the file pointer to the beginning of the file
def rewind
@file.rewind
end
def close
@file.colse
end
end
# Use the extend method to dynamically mix in modules for decoration
module TimeStampingWriter
def write_line(line)
super("#{Time.now}:#{line}")
end
end
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
最後に追加されるモジュールが最初に呼ばれ、その後、superを介して親クラスのwrite_lineメソッドが呼ばれます。
この例では、まずテキストにタイムスタンプのプリフィックスを付け、数字を追加し、最後にファイルに書き込んでいます。
sw = SimpleWriter.new("out3.txt")
sw.extend(NumberingWriter)
sw.extend(TimeStampingWriter)
sw.write_line("hello,ruby")
(2) aliasキーワードの使用
class SimpleWriter
def initialize(path)
@file = File.open(path,"w")
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
#characters
def pos
@file.pos
end
# It will point the file pointer to the beginning of the file
def rewind
@file.rewind
end
def close
@file.colse
end
end
Another dynamic way to implement the decoration pattern in ruby.
Modify the instance method of the object, so that the timestamp is added to the out1.txt file without affecting the object sw2, and the timestamp is not added to out2.txt.
sw1 = SimpleWriter.new("out1.txt")
class << sw1
alias old_write_line write_line
def write_line(line)
old_write_line("#{Time.now}:#{line}")
end
end
sw1.write_line("hello,world")
sw2 = SimpleWriter.new("out2.txt")
sw2.write_line("hello,world")
関連
-
MongoDBに接続するためのRuby on Railsフレームワークアプリケーション チュートリアル
-
Rubyのオブジェクト指向プログラミングでクラスとメソッドの基本を学ぶ
-
Jekyll静的ウェブサイトのバックエンドエンジンのチュートリアル
-
RubyのプログラムでXML形式のデータをパースするためにREXMLを呼び出す例
-
Builderビルダーパターンを用いたRubyデザインパターンプログラミング例
-
Rubyのデザインパターン。プログラミングにおけるシングルトンパターンの活用
-
Ruby on Railsのjquery_ujsコンポーネントが遅くなる問題が解決された
-
Ruby on Railsのメーラーの使い方を説明します。
-
Ruby on Railsのルーティング設定に関するいくつかのアドバイス
-
Ruby on RailsでMarkdownを使用する方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Ruby正規表現とサンプルコード
-
Rubyの二分探索(dichotomous search)アルゴリズムの簡単な例
-
Rubyにおけるフックメソッドとメソッド呼び出しへのフックの追加例
-
モンキーパッチのプログラミングスタイルとRubyでの利用について
-
Windowsでrubyとrailsをインストールする際に発生する問題点まとめ
-
RubyでXMLデータ処理ライブラリREXMLを使うための手引き
-
Ruby on Railsのパフォーマンスを最適化するためのいくつかの方法についての考察
-
Rubyにおけるコメントの使い方と中国語のエンコーディングについて解説します。
-
Ruby on Railsのビューの書き方に関するいくつかのアドバイス
-
Rubyバージョン管理ツールRVMのインストールとチュートリアルの使用方法