LoginSignup
17
23

More than 5 years have passed since last update.

Ruby デザインパターン

Last updated at Posted at 2016-12-01

概要

rubyでデザインパターンを一通り書いていく。

リポジトリは下記
https://github.com/colorbox/designpattern


Iterator

Iteratorは集合を数え上げる処理を共通化することで、集合を扱いやすくする。
また、集合の情報を隠蔽しすることで、ロジックの変更を行いやすくする。

ソース
iterator.rb
class Iterator

  def initialize(array)
    @array = array
    @index = 0
  end

  def has_next
    # compare with index
    return (@index + 1) <= @array.count
  end

  def next
    value = @array[@index]
    @index = @index + 1
    value
  end

end
main.rb
require_relative 'iterator'

iterator = Iterator.new(%w(hoge huga piyo))

while iterator.has_next
  puts(iterator.next)
end

Adaptor

既存のクラスの出力やインターフェースを必要な形式に変換する。
実現方法は継承によるものと委譲によるものの、大きく二通り存在する。

このパターンは、既にテストされたクラスをそのまま用いることによって安全性が担保される。

継承による実現

既存クラスのサブクラスを定義し、それをアダプタとして用いる方法。
サブクラスに新しいインターフェースを記述し、インターフェースを通してアダプタパターンを実現する。

委譲による実現

既存クラスのオブジェクトをアダプタオブジェクトに保持させる方法。
アダプタオブジェクトで定義されたメソッド内から必要に応じて既存クラスのメソッドを呼び出して、アダプタパターンを実現する。

継承と委譲について

個人的な所感ですが、既存クラスをそのまま流用して安全性を担保するという側面がある場合は、委譲による実現を行うほうが無難に思えます。

ソース
adaptor

class Adaptee
  def initialize
  end

  def hoge
    "hoge"
  end

end

class Adaptor
  def initialize
    @adaptee = Adaptee.new()
  end

  def hoge
    "<H1>" + @adaptee.hoge + "</H1>"
  end


end
main
require_relative 'adaptor'
adaptor = Adaptor.new()
puts adaptor.hoge

Template Method

処理の抽象化を行う。
処理の大枠を基底クラスで定義し、具体的な処理を各サブクラスに記載する。

事前に処理の大枠が決まっている場合に使用する。
大枠の処理を基底クラスで記述、その際使用されるメソッドはサブクラスでオーバーライドされる事が前提となる。

ソース
template_method

class TemplateMethod

  def initialize

  end

  def hoge
    hoge1
    print("\n")
    hoge2
    print("\n")
    hoge3
    print("\n")
  end

  def hoge1
  end

  def hoge2
  end

  def hoge3
  end

end

class TemplateMethodSub1 < TemplateMethod
  def hoge1
    print("--hoge1--")
  end

  def hoge2
    print("--hoge2--")
  end

  def hoge3
    print("--hoge3--")
  end

end

class TemplateMethodSub2 < TemplateMethod
  def hoge1
    print("**hoge1**")
  end

  def hoge2
    print("**hoge2**")
  end

  def hoge3
    print("**hoge3**")
  end

end

tm = TemplateMethod.new()
tm.hoge


tm1 = TemplateMethodSub1.new()
tm1.hoge

tm2 = TemplateMethodSub2.new()
tm2.hoge

Factory Method

特定のクラスに対して、そのインスタンスの生成を行うクラスを作成する。
作成するクラスを用意することで、インスタンス作成の前処理や後処理を追加できる。
Factory のインスタンス生成部分はTemplate Methodの応用でもある。

ソース
factory_method


class Product

  def initialize
  end

end

class ProductFactory

  def initialize
  end

  def create_product
    pre_create
    product = create
    post_create
    return product
  end

end

class ConcreteProductA < Product
end

class ConcreteProductAFactory < ProductFactory

  def pre_create
    print "pre A create\n"
  end

  def create
    print "A create\n"
    return ConcreteProductA.new
  end

  def post_create
    print "post A create\n"
  end
end

factory = ConcreteProductAFactory.new

factory.create_product


Singleton

インスタンスが一つしかないことを保証するパターン。
コンストラクタをprivateにしておき、クラスオブジェクトとして唯一のインスタンスを定義しておく。
その唯一のインスタンスを取得するメソッドを公開することで、そのクラスのインスタンスが一つしかないことを保証する。
但し、このパターンはグローバル変数的な扱われ方をするため、否定的に見られがち。

ソース
singleton

class Singleton

  @@singleton = self.new

  def self.get_singleton
    return @@singleton
  end

  private
  def initialize
  end

end

print(Singleton.get_singleton)
print("\n")
print(Singleton.get_singleton)
print("\n")
print(Singleton.get_singleton)
print("\n")


Prototype

生成が複雑なインスタンスをコピーして初期化する手法。
Rubyだとcloneメソッドがあるためそもそもパターンとして自分で実装することはないかも。

ソース
prototype
class Prototype

  attr_accessor :hoge

  @hoge = ""

  def initialize(str)
    @hoge = str
  end

end

pro = Prototype.new("hugahuga")
print pro.hoge
print pro

print("\n")
pro2 = pro.clone
print pro2.hoge
print pro2


Builder

複雑なインスタンスの生成を他のオブジェクト(Director)にまかせる。
また、生成を担当するDirectorクラスはBuilderクラスの公開メソッドを使用してインスタンスの生成を行う。
生成するインスタンスのバリエーションを増やす時は、Builerクラスを増やすことで対応する。
この時、DirectorとBuilderオブジェクトの関係はTemplate Methodと同様になる。

BuilderとTemplate Method

BuilderパターンとTemplate Methodの相違点は、処理の流れを把握しているのがどのクラスか、という点にある。
Template Methodパターンの場合は、処理の流れを把握しているのは基底クラスだが、Builderの場合はDirectorクラスとなる。

参考:
TemplateMEthodとBuilderの違いについて
http://hamasyou.com/blog/2004/06/09/builder/

ソース
builder.rb

class Director

  def initialize
  end

  def construct(builder)
    str = "hogehoge"
    str = builder.pre_operation(str)
    str = builder.operation(str)
    str = builder.post_operation(str)
    return str
  end

end

class LineBuilder
  def initialize
  end

  def pre_operation(str)
    "---" + str
  end

  def operation(str)
    "---" + str + "---"
  end

  def post_operation(str)
    str + "---"

  end

end

class BraceBuilder
  def initialize
  end

  def pre_operation(str)
    "{" + str

  end

  def operation(str)
    "(" + str + ")"
  end

  def post_operation(str)
    str + "}"
  end

end

b_builder = BraceBuilder.new
l_builder = LineBuilder.new

print Director.new.construct(b_builder)
print "\n"
print Director.new.construct(l_builder)


Abstract Factory

抽象化されたファクトリ。
FactoryとTemplate Methodを組み合わせたパターン。

ソース
abstract_factory.rb
class DinnaerFactory
  def initialize
  end

  def createDinner
    return [createAppetizer,createMain]
  end

  def createMain
  end

  def createAppetizer
  end
end

class Soup
  def initialize
  end
end

class Rice
  def initialize
  end
end

class Salad
  def initialize
  end
end

class Pizza
  def initialize
  end
end

class JapaneseFactory < DinnaerFactory
  def createMain
    Rice.new
  end

  def createAppetizer
    Soup.new
  end
end

class ItalyFactory < DinnaerFactory
  def createMain
    Pizza.new
  end

  def createAppetizer
    Salad.new
  end
end

japanese_f = JapaneseFactory.new
print japanese_f.createDinner
print "\n"

italy_f = ItalyFactory.new
print italy_f.createDinner

参考
http://futurismo.biz/archives/2805


Bridge

クラスにおける「実装」と「機能」を分割する。
クラスを、自分のメソッドのみで実現可能な「機能」と、それらのメソッドを記述した「実装」とに分割する。
「実装」はクラス内に委譲オブジェクトとして保持しておく。
このオブジェクトを切り替えることで機能と実装を分離し、容易にバリエーションを増やすことが可能。
Template Methodの応用とも取れる。

ソース
bridge.rb
class OutputImpleHoge
  def initialize
  end
  def output
    "hoge"
  end
end

class OutputImpleOptional
  @option = ""
  def initialize(str)
    @option = str
  end
  def output
    @option
  end
end

class Output

  attr_accessor :impl
  @impl = nil

  def initialize(impl)
    @impl = impl
  end

  def output
    @impl.output
  end

  def multiple
    [@impl.output,@impl.output,@impl.output]
  end

end

op = Output.new(OutputImpleHoge.new)
print op.output
print"\n"
print op.multiple
print"\n"

opo = Output.new(OutputImpleOptional.new("piyo"))
print opo.output
print"\n"
print opo.multiple
print"\n"

Strategy

処理を切り替える。
「処理」をオブジェクトとして切り出すことで、その切替を容易にするパターン。
例えば、シューティングゲームの敵の弾パターンや、敵の移動パターンの切り替えに使える。

ソース
strategy.rb
class Enemy

  attr_accessor :move_pattern, :x, :y

  def initialize(move_pattern)
    @move_pattern = move_pattern
    @x = 0
    @y = 0
  end

  def move
    @move_pattern.run(self)
  end

end

class SideStraghtPattern
  def initialize
  end

  def run(enemy)
    enemy.x = enemy.x+1
  end
end

class StraightPattern
  def initialize
  end

  def run(enemy)
    enemy.y = enemy.y+1
  end
end

enemy = Enemy.new(StraightPattern.new)
print enemy.x.to_s + "," + enemy.y.to_s + "\n"
enemy.move
print enemy.x.to_s + "," + enemy.y.to_s + "\n"
enemy.move
print enemy.x.to_s + "," + enemy.y.to_s + "\n"

enemy = Enemy.new(SideStraghtPattern.new)
print enemy.x.to_s + "," + enemy.y.to_s + "\n"
enemy.move
print enemy.x.to_s + "," + enemy.y.to_s + "\n"
enemy.move
print enemy.x.to_s + "," + enemy.y.to_s + "\n"

Composite

入れ子や木のような再帰構造を実現するパターン。
基底クラスを継承した枝葉クラスを使用して、再帰構造を実現する。

ソース
composite.rb
class Entry
  def initialize(name)
    @name = name
  end
  def add
  end
  def prints
    print @name + "\n"
  end
end

class CompositeFile < Entry
end

class Directory < Entry
  def initialize(name)
    super(name)
    @entries = []
  end
  def add(entry)
    @entries.push(entry)
  end

  def prints
    super
    @entries.each do |entry|
      entry.prints
    end
  end

end

dir1 = Directory.new("dir1")
dir1.add(CompositeFile.new("1"))
dir1.add(CompositeFile.new("2"))
dir1.add(CompositeFile.new("3"))

dir1.prints


Decorator

オブジェクトを保持しておき、その処理に機能追加を行う。
この構造が階層構造になるような場合のパターン。
単なるwrapperを階層的にしたパターンとも言える。
基底クラスを保持して階層構造を実現するという点でCompositeパターンと同じ。
Compositeパターンを内包したパターンとも言える。

ソース
decorator.rb
class BaseOutputer

  def initialize(str)
    @str = str
  end

  def run
    return @str
  end

end

class Decorator < BaseOutputer
  def initialize(outputer)
    @outputer = outputer
  end

end
class LineDecorator < Decorator
  def run
    return "|" + @outputer.run + "|"
  end
end

class CurlyBraceDecorator < Decorator
  def run
    return "{" + @outputer.run + "}"
  end
end

ld = LineDecorator.new(BaseOutputer.new("hoge"))
print ld.run + "\n"
cd = CurlyBraceDecorator.new(ld)
print cd.run

Visitor

処理と構造を分割するパターン。
処理クラスと構造クラスを用意する。

このパターンは処理に対して開いており、構造に対して閉じた実装となる。
処理の追加は楽だが、構造を修正すると、それに付随して全ての処理クラスを修正しなくてはならなくなるため、手間が大きい。

参考
http://objectclub.jp/community/memorial/homepage3.nifty.com/masarl/article/dp-ocp-2.html

ソース
visitor.rb
class Entry
  def initialize(name)
    @name = name
  end
  def add
  end
  def prints
    @name
  end
end

class CompositeFile < Entry
  def accept(visitor)
    visitor.visit(self)
  end
end

class Directory < Entry
  def initialize(name)
    super(name)
    @entries = []
  end
  def add(entry)
    @entries.push(entry)
  end

  def prints
    @name
  end

  def members
    @entries
  end

  def accept(visitor)
    visitor.visit(self)
  end

end

class Visitor
  def initialize
  end
  def visit(entry)
    if entry.class.to_s =="Directory"
      visit_directory(entry)
    else
      visit_file(entry)
    end
  end
end

class UpcaseVisitor < Visitor
  def visit_file(entry)
    print entry.prints.upcase + "\n"
  end
  def visit_directory(entry)
    print entry.prints.upcase + "\n"
    entry.members.each do |member|
      member.accept self
    end
  end
end

class DowncaseVisitor < Visitor
  def visit_file(entry)
    print entry.prints.downcase + "\n"
  end
  def visit_directory(entry)
    print entry.prints.downcase + "\n"
    entry.members.each do |member|
      member.accept self
    end
  end
end

dir1 = Directory.new("dir1")
dir1.add(CompositeFile.new("a1"))
dir1.add(CompositeFile.new("b2"))
dir1.add(CompositeFile.new("C3"))

up_visitor = UpcaseVisitor.new
dir1.accept up_visitor

down_visitor = DowncaseVisitor.new
dir1.accept down_visitor

Chain of Responsibility

責任のたらい回し。
特定の処理が可能なオブジェクトに行き着くまでその処理をたらい回しにする。

ソース
chain_of_responsibility
class Problem
  attr_accessor :difficulty
  def initialize(difficulty)
    @difficulty = difficulty
  end
end

class Resolver
  attr_accessor :next
  def initialize
    @next = nil
  end
end

class OddResolver < Resolver
  def resolve(problem)
    if problem.difficulty%2==1
      print("resolved by odd\n")
      true
    else
      print("odd cannot resolve\n")
      return false unless @next
      @next.resolve(problem)
    end
  end
end

class WeakResolver < Resolver
  def resolve(problem)
    if problem.difficulty < 10
      print("resolved by weak\n")
      true
    else
      print("weak cannot resolve\n")
      return false unless @next
      @next.resolve(problem)
    end
  end
end

class StrongResolver < Resolver
  def resolve(problem)
    if problem.difficulty < 100
      print("resolved by strong\n")
      true
    else
      print("strong cannot resolve\n")
      return false unless @next
      @next.resolve(problem)
    end
  end
end


odd_r = OddResolver.new
wr = WeakResolver.new
sr = StrongResolver.new

odd_r.next = wr
wr.next = sr

pro1 = Problem.new(11)
odd_r.resolve(pro1)
print"---\n"
pro2 = Problem.new(8)
odd_r.resolve(pro2)
print"---\n"
pro3 = Problem.new(100)
odd_r.resolve(pro3)
print"---\n"


Facade

クラスのAPIを制限するパターン。
特定のクラスのオブジェクトに対して、呼び出せるメソッドを少なくすることで、呼び出しやすくする。
一般的なカプセル化、情報隠蔽とも言える。


Mediator

多くの要素がある際の、ご意見役として機能するオブジェクトを用意する。
複数のクラスが相互に連動して動くような場合、一括で管理及び処理を行うオブジェクトを用意して、そのオブジェクトを中心に動作をおこなわせる。
個々のオブジェクトがやり取りを行うと、カプセル化が壊れてしまうためである。
iOSにおけるUIViewControllerなどがこれにあたる。

ソース
mediator.rb

class PartsManager
  attr_accessor :door, :switch
  def collegue_changed
    return if (@switch.on && @door.is_open)||(!@switch.on && !@door.is_open)
    if @switch.on
      @door.open
    else
      @door.close if @door.is_open
    end
  end
end

class Part
  attr_accessor :mediator
  def initialize(mediator)
    @mediator = mediator
  end
end

class Door < Part
  attr_accessor :is_open
  def initialize(mediator)
    super(mediator)
    @is_open = false
  end
  def open
    print "ドアが空いた\n"
    @is_open = true
    @mediator.collegue_changed
  end
end

class Switch < Part
  attr_accessor :on
  def initialize(mediator)
    super(mediator)
    @on = false
  end
  def push
    print "ボタンが押された\n"
    @on = true
    @mediator.collegue_changed
  end
end

pm = PartsManager.new

door = Door.new(pm)
switch = Switch.new(pm)

pm.door = door
pm.switch = switch

print pm.door
print pm.switch


switch.push


Observer

オブジェクトの監視を行うパターン。
監視対象のオブジェクトに変化が発生した時、その変化を他のオブジェクトに通知させたい時に使用する。
GUIなどはその典型例。
Rubyにはこのパターンを実現するためのObservableモジュールが存在する。

ソース
observer.rb
class NumberGenerator

  attr_accessor :observer, :number
  def initialize(observer)
    @observer = observer
    @number = 0
  end
  def update
    observer.notify(self)
  end
  def execute
    @number = Random.new.rand(20)
    update
  end
end

class NumberObserver
  def notify(generator)
    print(generator.number)
  end
end

o = NumberObserver.new
g = NumberGenerator.new(o)

(1..100).to_a.each do
  g.execute
  print(",")
end

Memento

状態を保存するパターン。
特定タイミングにおけるオブジェクトの状態を保存しておき、それを復元可能とするパターン。
復元可能という点が、単夏履歴とは異なる。
また、このパターンに関連して、「狭いインターフェース」「広いインターフェース」という考え方が存在する。

広いインターフェースと狭いインターフェース

この2つの単語は、元々GoFがこのパターンでのみ補足的に用いている用語である。
状態を保存する際は、保存に必要な情報を全て知っておかなくてはいけない。(wide interface)
また、状態保存を行わない外部のオブジェクトに対しては、適切に情報隠蔽を行う。(narrow interface)

ソース
memento.rb
class Memento
  attr_accessor :money
  def initialize(money)
    @money = money
  end
end

class Gamer
  attr_accessor :money
  def initialize
    @money = 0
  end
  def save
    return Memento.new(money)
  end
  def load(memento)
    @money = memento.money
  end
end

g = Gamer.new
print(g.money)
print("\n")
g.money = 200
m = g.save
print(g.money)
print("\n")
g.money = 19
print(g.money)
print("\n")
g.load(m)
print(g.money)
print("\n")

State

状態を表現するパターン。
状態クラスを定義し、それらを切り替えることで状態遷移などを実現する。
次にどの状態に遷移するかという情報が各状態クラスの中に記述されている。
状態数が多くなりすぎると状態遷移図などが必要になる。
各状態でやらせる処理が単純な場合、このパターンを適用するのは逆に手間となる。
その場合は単純なenumとswitchで良い。
各状態で行わせる処理が煩雑な場合、switchで処理をさせようとするとコードが非常に長くなる。
そのような場合にのみこのパターンを適用すべきだろう。

ソース
state.rb

class State
  attr_accessor :count, :context
  def initialize(context)
    @count = 0
    @context = context
  end
end

class RunState < State
  def do_something
    print"running\n"
    @count = @count+1
    if @count > 3
      @context.change_state(StopState.new(@context))
    end
  end
end

class StopState < State
  def do_something
    print"stoping\n"
    @count = @count+1
    if @count > 3
      @context.change_state(RunState.new(@context))
    end

  end
end

class Person
  attr_accessor :state
  def initialize
  end
  def set_state(state)
    @state = state
  end
  def change_state(state)
    @state = state
  end
  def do_something
    @state.do_something
  end
end

p = Person.new
p.set_state(StopState.new(p))

(1..10).each do
  p.do_something
end


Flyweight

リソースの使い回しを行うためのパターン。
いちいち生成していると時間がかかってしまうような場合に使用する。
キャッシュなどが近い。

ソース
flyweight.rb
class Flyweight
  attr_accessor :pool
  def initialize
    @pool = {}
  end
  def get_number(key)
    if @pool[key]
      print "use cache #{key}\n"
      return @pool[key]
    end
    print "created #{key}\n"
    @pool[key] = key
    return @pool[key]
  end
end

f = Flyweight.new

f.get_number(1)
f.get_number(1)
f.get_number(2)
f.get_number(2)


Proxy

データアクセスにおける中間層を定義する。
中間層に付随的な処理を記述することにより、データアクセスそのものとそれに付随する処理を分離できる。

ソース
proxy.rb
class RealObject
  attr_accessor :secret_info
  def initialize(secret_info)
    @secret_info = secret_info
  end
end

class Proxy
  attr_accessor :real_object
  def initialize(real_object)
    @real_object = real_object
  end
  def secret_info
    print "warning use secret_infoz\n"
    return @real_object.secret_info
  end
end



p = Proxy.new(RealObject.new("secret"))

print p.secret_info

Command

命令とそれに紐づく処理をクラスとして切り出すパターン

ソース
command.rb
class Target
  attr_accessor :value
  def initialize(value)
    @value = value
  end
end

class Command
  def execute
  end
end

class PlusCommnad < Command
  attr_accessor :target, :value
  def initialize(target,value)
    @target = target
    @value = value
  end
  def execute
    @target.value = @target.value + @value
  end
end

class Multicommand < Command
  def initialize(target,value)
    @target = target
    @value = value
  end
  attr_accessor :target, :value
  def execute
    @target.value = @target.value * @value
  end
end

class CompositeCommand < Command
  attr_accessor :commands
  def initialize(commands)
    @commands = commands
  end
  def add_command
    @commands.push(command)
  end
  def execute
    @commands.each do |command|
      command.execute
    end
  end

end

t = Target.new(10)
cc = CompositeCommand.new([PlusCommnad.new(t,1),Multicommand.new(t,2)])
print(t.value)
print"\n"
cc.execute
print(t.value)
print"\n"



Interpreter

中間言語を表現するためのpターン、使い所は限られるため割愛。

参考

http://www.techscore.com/tech/DesignPattern/index.html/
http://morizyun.github.io/blog/categories/design-pattern/
http://www002.upp.so-net.ne.jp/ys_oota//mdp/index.htm
http://www.blackwasp.co.uk/gofpatterns.aspx

17
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
23