Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

RubyでDecoratorパターン

概要

GoFデザインパターンのDecoratorパターンについて。
飾りと中身を同一視するパターン。

利点

元のクラスの中身を変えずに機能を追加出来る
組み合わせで様々な機能を実現できる

欠点

小さいクラスが大量に出来る

登場人物

Component = 抽象コンポーネント。構成要素
ConcreteComponent = 具象コンポーネント。構成要素
Decorator = 抽象飾り。構成要素をメンバに持つ
ConcreteDecorator = 具象飾り。構成要素をメンバに持つ

UML


実装サンプル

サンプル概要

HTMLの構成をDecoratorで実装します。
HTMLの表示要素となるタグと装飾要素になるタグおよびCSSComposite=構成要素として扱います。

例えば構成要素としてDIV,MARQUEE(スライド表示する文字列),HR(横線)。
それらをタグで装飾するB,CENTERタグ。

これらを組み合わせて

  • 太字でスライド表示される文字列
  • 太字で背景が赤で文字が黄色のスライド表示される文字列
  • センタリングされたDIV
  • センタリングされた太字のDIV
  • センタリングされて太字で背景が黄色で文字が赤のDIV

などを作成してみます。

登場人物

Component = WebComponent
Component = Tag
ConcreteComponent = Div
ConcreteComponent = Hr
ConcreteComponent = Marquee
Decorator = DecorateTag
ConcreteDecorator = Bold
ConcreteDecorator = Center

UML


サンプルコード

WebComponent

# encoding: Shift_JIS
=begin rdoc
= WebComponentクラス
=end
class WebComponent
  # mapは各種タグ、Styleに設定が必要なパラメーターを自由に設定する
  attr_accessor :name,:value,:map
  NOT_OVERRRIDE = 'not override error'
  def initialize(name,value,map)
    @name = name
    @value = value
    @map = map
  end

  def output()
    raise WebComponent::NOT_OVERRRIDE
  end
end

Tag

# encoding: Shift_JIS
require_relative './web_component'

=begin rdoc
= Tagクラス
=end
class Tag < WebComponent
  attr_accessor :has_close_tag
  def initialize(name,value,map,has_close_tag)
    @has_close_tag = has_close_tag
    super(name,value,map)
  end

  def output()
    html = ""
    if @has_close_tag
      html << "<#{@name}>#{@value}<\/#{@name}>"
    else
      html << "<#{@name} />"
    end
    return html
  end
end

Div

# encoding: Shift_JIS
require_relative './tag'

=begin rdoc
= DIVタグクラス
=end
class Div < Tag
  def initialize(value,map)
    super("div",value,map,true)
  end
end

Hr

# encoding: Shift_JIS
require_relative './tag'

=begin rdoc
= HRタグクラス
=end
class Hr < Tag
  def initialize()
    super("hr",nil,nil,false)
  end
end

Marquee

# encoding: Shift_JIS
require_relative './tag'

=begin rdoc
= MARQUEEタグクラス
=end
class Marquee < Tag
  def initialize(value,map)
    super("marquee",value,map,true)
  end
end

DecorateTag

# encoding: Shift_JIS
require_relative './tag'

=begin rdoc
= DecorateTagクラス
=end
class DecorateTag < Tag
  attr_accessor :web_component
  def initialize(name,map,web_component)
    @web_component = web_component
    super(name,nil,map,true)
  end

  def output()
    html = ""
    html << "<#{@name}>"
    html << @web_component.output
    html << "<\/#{@name}>"
    return html
  end
end

Bold

# encoding: Shift_JIS
require_relative './decorate_tag'

=begin rdoc
= Bタグクラス
=end
class B < DecorateTag
  def initialize(map,web_component)
    super("b",map,web_component)
  end
end

Center

# encoding: Shift_JIS
require_relative './decorate_tag'

=begin rdoc
= CENTERタグクラス
=end
class Center < DecorateTag
  def initialize(map,web_component)
    super("center",map,web_component)
  end
end

main

# encoding: Shift_JIS
require_relative './div'
require_relative './hr'
require_relative './b'
require_relative './marquee'
require_relative './center'

div_param_map = {"test"=>1}
div = Div.new("divの中身",div_param_map)
puts div.output

marquee_param_map = {"test"=>1}
marquee = Marquee.new("marqueeの中身",marquee_param_map)
puts marquee.output

hr = Hr.new
puts hr.output

b_param_map = {"test"=>1}
b = B.new(b_param_map,div)
puts b.output

b = B.new(b_param_map,marquee)
puts b.output

center_param_map = {"test"=>1}
center = Center.new(center_param_map,div)
puts center.output

center = Center.new(b_param_map,b)
puts center.output
出力結果
<div>divの中身</div>
<marquee>marqueeの中身</marquee>
<hr />
<b><div>divの中身</div></b>
<b><marquee>marqueeの中身</marquee></b>
<center><div>divの中身</div></center>
<center><b><marquee>marqueeの中身</marquee></b></center>