銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

当ブログではアフィリエイト広告を利用しています

【Rubyによるデザインパターンまとめ9】デコレータパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第9弾です。(毎週1つが目標です!)

前回の記事(プロキシパターンのまとめ)はこちらです。
【Rubyによるデザインパターンまとめ8】プロキシパターン - 銀行員からのRailsエンジニア

この本で紹介されているサンプルコードそのままではなく、オリジナルのコードで説明しています。

今回は デコレータ(Decorator)パターン についてまとめました。

f:id:ysk_pro:20200104144100p:plain

デコレータパターンとは

既存のオブジェクトに、機能を簡単に追加するためのパターンです。

層状に機能を積み重ねることができ、状況ごとに必要な機能のみを持つオブジェクトを作ることができます。

Decorator という単語は「装飾者」という意味で、元のオブジェクトに必要な機能を装飾するイメージです。

文章の説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。

サンプルコード

通知を行うプログラムがあるとします。

元々はサービス内での通知だけだったものの、ユーザーの要望で 通知のタイミングでSlack通知、メール通知、またその両方について既存の通知に追加して行う必要があるケースを考えてみます。

デコレータパターンを使うと以下のようにできます。

class Notifier # 元々存在した通知を行うクラス
  def send(message)
    puts "サービス内で「#{message}」を通知するよ"
  end

  def stop
    puts '通知を止めるよ'
  end
end

class NotifierDecorator # デコレータの基底クラス
  attr_reader :notifiler

  def initialize(notifiler)
    @notifiler = notifiler
  end

  def send(message)
    notifiler.send(message)
  end

  def stop
    notifiler.stop
  end
end

class SlackNotifier < NotifierDecorator # slack通知を行うデコレータ
  def send(message)
    puts "slackで「#{message}」を通知するよ"
    notifiler.send(message)
  end
end

class MailNotifier < NotifierDecorator # メール通知を行うデコレータ
  def send(message)
    puts "メールで「#{message}」を通知するよ"
    notifiler.send(message)
  end
end

次のように実行します。

notifier = MailNotifier.new(SlackNotifier.new(Notifier.new))
notifier.send('投稿にいいねがついたよ')
puts '---'
notifier.stop

メール通知・Slack通知を追加したい時に「MailNotifier.new(SlackNotifier.new(Notifier.new))」のように層状に機能を追加していることが分かると思います。

このように、実行時に必要な機能を組み合わせることができます。

実行結果はこうなります。

メールで「投稿にいいねがつきました!」を通知するよ
slackで「投稿にいいねがつきました!」を通知するよ
サービス内で「投稿にいいねがつきました!」を通知するよ
---
通知を止めるよ

仮にデコレータパターンを使わずに シンプルに継承を使って実現しようとすると、組み合わせごとにクラスを作る必要が出てくるので辛くなります...


また、デコレータの基底クラス(NotifierDecoratorクラス)は、Notifierクラスに処理を移譲しているだけなので、次のようにリファクタリングすることができます。

require 'forwardable'

class NotifierDecorator # デコレータの基底クラス
  extend Forwardable
  attr_reader :notifiler

  def_delegators :notifiler, :send, :stop

  def initialize(notifiler)
    @notifiler = notifiler
  end
end

Forwardableモジュールの def_delegatorsクラスメソッドは、1つ目の引数にインスタンス名を取り、そのインスタンスに2つ目以降の引数にとったメソッドを移譲します。

これにより、1つ1つのメソッドに移譲の処理を書く必要がなくなりました。

実行の仕方、実行結果は先ほどと同じになります。

おわりに

ここまで読んでいただきありがとうございます。

Rubyによるデザインパターン の中では、いくつかのサンプルコードを用いた説明、またこの記事で紹介しきれなかったリファクタリングについても解説しているので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

次回は、シングルトン(Singleton)パターンをまとめます。

来週も頑張ります!

(追記)
シングルトンパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com