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

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

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

【Rubyによるデザインパターンまとめ6】コマンドパターン

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

前回の記事(イテレータパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

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

今回は コマンド(Command)パターン についてまとめました。

f:id:ysk_pro:20191214214952p:plain

コマンドパターンとは

処理の内容をオブジェクトに閉じ込めて、実行する際はそのオブジェクトのメソッドを呼び出すパターン。

処理の内容が書いたオブジェクトのことを、命令という意味の「コマンド」と呼んでいます。

これにより複数のコマンドをキューに入れて順に実行するようにしたり、処理の取り消しなどをシンプルに実装できるようになります。

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

サンプルコード

色々なことの勉強方法を出力する簡単なプログラムを考えてみます。(英語とAtCoder(競技プログラミング)の勉強頑張ってます)

コマンドパターンで実装した例がこちらです。

class Command
  def execute
    raise 'メソッドが定義されていないよ'
  end
end

class EnglishCommand < Command
  def execute
    puts 'オンライン英会話レッスンを行う'
  end
end

class AtcoderCommand < Command
  def execute
    puts '過去問を解く'
  end
end

class Study
  attr_reader :command

  def initialize(command)
    @command = command
  end

  def do
    command.execute
  end
end

Study.new(EnglishCommand.new).do
Study.new(AtcoderCommand.new).do

Studyクラスのインスタンスを作る際に引数として渡しているオブジェクトが「コマンド」です。

Studyクラスのインスタンスを実行すると、コマンドの処理が実行されます。

実行結果は次のようになります。

オンライン英会話レッスンを行う
過去問を解く

ストラテジーパターンとの違い

ここまで読んで、「あれ?ストラテジーパターンと一緒じゃない?」と思った方がいると思います。(僕も思いました。)

(ストラテジーパターンについての記事はこちら)
ysk-pro.hatenablog.com

コマンドパターンとストラテジーパターンには次の違いがあるそうです。(難しい...)

  • コマンドパターンは、任意の処理をコマンドとしてオブジェクト化する。そしてそのコマンドをキューに入れたり、複数のコマンドを一括で呼び出したりする場合によく使われる。
  • ストラテジーパターンは、同じことを様々な方法を用いて行う場合に用いられ、これらのアルゴリズムを入れ替える必要がある場合によく使われる。

おわりに

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

Rubyによるデザインパターン の中では、様々な例を用いて説明がされていると共に、この記事には書ききれなかった処理の取り消しなどをシンプルに実装しているコードなどが載っているので、ご興味ある方は是非合わせてご覧ください。

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

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

また、英語ですがこちらの記事の説明が分かりやすかったです。
refactoring.guru

次回は、アダプターパターンをまとめます。

来週も頑張ります!

(先週のイテレータパターンをまとめた記事はこちら)
ysk-pro.hatenablog.com

AWS re:Invent 2019に参加してきました!体験したことや感想など(雰囲気が伝わるように写真多め)

2019/12/2 〜 2019/12/6 の 5日間 ラスベガスで行われた AWS のイベント「re:Invent」に参加してきました!
 
忘れないうちに、体験したことや感想を残しておきます。
 
写真をたくさん載せているので、re:Invent 会場やラスベガスの雰囲気を感じられると思います。

f:id:ysk_pro:20191208025534p:plain

re:Invent とは

AWSAmazon Web Service)が主催するイベントで、毎年ラスベガスの複数のホテルで行われており、今回で7回目の開催となります。

「学習型カンファレンス」であることが特徴で、聴くだけのセッションだけではなく、実際に手を動かすハンズオン形式のセッションが多く行われていました。セッション数はなんと3,000以上ありました。

世界中から65,000人以上、日本からも1,700人以上の参加者がいたようです。 

こちらは、メイン会場の1つであるベネチアンホテル内の会場入口の様子です。

ホテル内の入口ゲートの写真

 

印象に残ったセッション

基本方針として「現地でしかできないことをしよう」と思い、後から動画配信されるであろう聴くだけのセッションにはほとんど参加せず、AWS社員に質問しながら手を動かせるワークショップや、雰囲気を感じられるKeynoteを中心に参加してきました。
その中で、印象に残ったセッションをいくつか紹介します

DeepRacerのワークショップ

DeepRacerとは、re:Invent 2018 で発表された、機械学習を楽しく学ぶためのサービスで、機械学習で学習させながらラジコンカーを仮想環境、実環境で走らせるものです。

re:Invent 2019 では、DeepRacerの大規模なコンテストも行われていました。

このワークショップでは、学習させながら仮想環境でラジコンカーを走らせるところまでを行いました。

DeepRacerのキモとなる報酬関数のチューニングまでは行わないものの、機械学習初心者の僕が、簡単にDeepRacerを動かすことができ、とても印象に残りました。

ワークショップは2時間程度で、最初にサービスについての簡単な説明があり、その後教材を見ながら実際に動くところまで手を動かします。その中で適宜 AWS社員のサポートを受けることができます。

また、教材は配布されるので、後からじっくりと取り組むこともできます。

 

Alexaのワークショップ

アレクサのスキル(スマホで言うところのアプリ)を実際に作るワークショップです。

誕生日を覚えてくれるアレクサスキルを作ったのですが、簡単に作れてしまい驚きました。

 

Keynote(Andy Jassy / AWS CEO)

大きな会場で、DJやライブ演奏を挟みつつ新サービスなどが続々と発表されるKeynoteは圧巻でした。

大変な盛り上がりで、世界中のエンジニアの熱量を直に感じることができました。

Keynote会場の様子

 

Hands on lab

各席にPCが1台ずつセットされており、自分の触ってみたいサービスのハンズオンができるセッションです。ワークショップと同様にAWS社員に適宜質問をすることができます。

触ってみたかった「DynamoDB」と「AWS Lambda」のハンズオンを行いました。

このような雰囲気で、世界中のエンジニアが黙々とハンズオンに取り組んでいます。

ハンズオン会場の様子

 

ラスベガス観光編

せっかくラスベガスに行ったので、夜の時間などを使って、観光も楽しんできました。

カジノ

ラスベガスと言えばカジノですよね。せっかくなのでやってみたいと思い、日本で予習してから臨みました。
 
スロット、ルーレット、ブラックジャックをやりましたが、 完全に運ゲーであるスロット、ルーレットよりも、自分に選択肢のあるブラックジャックにハマりました。
ディーラーや、隣のプレイヤーとの交流も楽しかったです。
2日間合わせて数時間遊んだのですが、奇跡的に少し勝てました!(ビギナーズラックですね)
 
カジノはとても明るく綺麗で、日本のゲームセンターと同じような雰囲気に感じました。

カジノの写真

カジノの写真

 

シルクドソレイユ

シルクドソレイユとは、ラスベガスの有名なサーカスショーです。
複数のホテルにてショーが常設されており、最も有名なショーの1つである「O(オー)」を観てきました。
 
プールになったり床になったりする舞台、超絶技巧、笑いも挟みながらの1.5時間はあっという間でした。
日本で予約していったので中央 / 前から3列目の席で演者の表情までよく見れて、とても楽しめました。
 
ショーの途中の写真撮影は禁止であり、こちらは終了時の写真です。

シルクドソレイユ、ショー終了時の写真

 

ラスベガスの写真

ラスベガスで色々と写真を撮ったので、雰囲気が感じていただけるように貼っておきます。

 

さすがラスベガス、空港についた瞬間こんな感じでした。

ラスベガスの空港にカジノ台が設置されている様子

 

様々な街をモチーフにしたホテルがあり、散歩しているだけでも楽しいです。

パリをモチーフにしたホテルの写真

ニューヨークをモチーフにしたホテルの写真

 

オーシャンズ11のラストシーンで有名なベラージオホテルの噴水ショーです。毎回内容が変わり何度見ても飽きませんでした。

ベラージオホテルの噴水ショーの写真

 

今回宿泊したフラミンゴホテルです。フラミンゴホテルという名前の通り、中庭に普通にフラミンゴがいて面白かったです。

フラミンゴホテルの外観

フラミンゴホテルにいたフラミンゴの写真

 

re:Invent開催中の昼食はこのような大きな会場、もしくはサンドウィッチなどのランチボックスで提供されました。

昼食会場全体の様子

 

ダウンタウンにも行ってみました。

ダウンタウンの写真

 

アフターパーティー(re:Play)の様子です。DJの演奏や様々なアクティビティがありました。規模がすごい。

re:Play全体の写真

re:PlayでのDJ演奏の様子

re:Playのアクティビティ

 

その他に思ったこと

とにかくたくさん歩く

ホテル間の距離が遠く、またホテルが大きすぎるのでホテル内でも、とにかく歩きます。

平均で 1日15㎞ 以上歩いていました。

 

UBER便利

前述の通りとにかく歩いて疲れるので、終盤はちょっとした移動にもUBERを使うようになりました。

UBER初体験でしたが、体験がとても良くて驚きました。日本でも早く使いたい。。
 

おわりに

会社や個人でAWSを使っており、認定資格(AWS - SAA)も保有しているのですが、知らないサービスがたくさんあり、ワークショップで触ることができ面白かったです。
 
発表された新サービスは機械学習関連が多く、業界の流れを感じることができました。
 
また、英語があまりできないのはやはり辛かったので、英語学習は続けていこうと思います。(re:Invent 前の2ヶ月間ほぼ毎日オンライン英会話をやっていたので、それを継続します!)
 
色々なサービスに触れられたので、忘れないうちに会社・個人で積極的に使っていこうと思っています!
 
現地でお世話になった皆さまありがとうございました!
 
AWSの資格取得時に書いたブログも、よければ合わせてご覧ください。

【Ruby】後置ifが末尾にあるメソッドの返り値はなに...?

業務で少しハマったことをTwitterでつぶやいてみました。

割とバラけていますね。

flag が true であればもちろん Good night が答えになるのですが、これはどうなるでしょうか





(↓に答え)





答えはそれ以外で「nil」になります。

それ以外を選ばれた方、さすがでした!(nil を選択肢に入れなかったのがちょっと卑怯だったかも)

後置if で false になる時の返り値についてはドキュメントに記載がありました。

右辺の条件が成立する時に、左辺の式を評価してその結果を返します。 条件が成立しなければ nil を返します。

制御構造 (Ruby 2.6.0) の if 修飾子 の部分です。

なるほど。



また、こちらはどうなるでしょうか。

def greeting(flag)
  'Good evening'
  if flag
    'Good night'
  end
end
p greeting(false) # -> ?





(↓に答え)





こちらも「nil」になります。

if 節の返り値についても、先ほどと同じドキュメントに記載がありました。

if 式は、条件が成立した節(あるいは else 節)の最後に評価し た式の結果を返します。else 節がなくいずれの条件も成り立たなけれ ば nil を返します。

制御構造 (Ruby 2.6.0)

へぇ、そうなるんだ〜。

これを認識していないと予想外のバグを埋め込んでしまうおそれがあるので注意したいですね。

おわり

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

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

前回の記事(Compositeパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

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

今回は イテレータIterator)パターン についてまとめました。

f:id:ysk_pro:20191124121757p:plain

イテレータパターンとは

一言で言うと、オブジェクトの集まりがあった時に、そのオブジェクト1つずつに順番にアクセスする方法を提供するデザインパターンです。

GoFは、以下のように説明しています。

集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する

GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)

イテレータは内部イテレータと外部イテレータの2つに大別できます。

  • 内部イテレータ繰り返しの制御を自分で書く必要がなくイテレータ自身が制御するもの。コードブロックベースのイテレータで、eachメソッドが内部イテレータである。メリットはシンプルに記載できること
  • 外部イテレータ繰り返しの制御自分で書く必要があるもの。メリットは単純な繰り返し以外の制御をすることができること

サンプルコード

配列で与えられたデザインパターンを1つずつ出力する、簡単なプログラムを考えてみます。

内部イテレータでの実装

design_patterns = ['template_method', 'strategy', 'observer']
design_patterns.each { |design_pattern| puts "#{design_pattern} is one of design patterns" }

eachメソッドをシンプルに使うだけなので簡単です!
実行結果はこのようになります。

template_method is one of design patterns
strategy is one of design patterns
observer is one of design patterns

外部イテレータでの実装

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

  def has_next? # 次の要素があるかどうか
    @index < @array.length
  end

  def next_item # 次の要素を返して、indexを1進める
    value = @array[@index]
    @index += 1
    value
  end
end

design_patterns = ['template_method', 'strategy', 'observer']

i = ArrayIterator.new(design_patterns)
while i.has_next?
  puts "#{i.next_item} is one of design patterns"
end

外部イテレータでは、ArrayIteratorクラスのインスタンスを使って、繰り返しの制御を自分で行なっていることが分かると思います。
実行結果は、内部イテレータと同じです。

おわりに

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

Rubyにはeachメソッドがあるので、イテレータパターンはとても簡単に実装できますね。

Rubyによるデザインパターン の中では、様々なサンプルコードを用いて説明されており、さらに使用する際の注意点についても言及されていたので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、コマンドパターンをまとめます。

来週も頑張ります!

(先週のCompositeパターンをまとめた記事はこちら)
ysk-pro.hatenablog.com

【Rubyによるデザインパターンまとめ4】Compositeパターン

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

前回の記事(オブザーバーパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

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

今回は Composite(コンポジット)パターン についてまとめました。

f:id:ysk_pro:20191117170819p:plain

Compositeパターンとは

あるものと、それが集まってできたものを同じように扱うことができるデザインパターンです。

例えば、会社は人が集まって課ができており、課が集まって部ができており、部が集まって会社ができています。人が会社から給料を受け取っているのと同じように、人の集まりである課や部も会社から給料を受け取っていると考えることができ、人・課・部が受け取っている給料を共通のインターフェースで扱うことができるイメージです。

Rubyによるデザインパターン のこちらの説明はわかりやすかったです。

GoF「全体が部分のように振る舞う」という状況を表すデザインパターンを、Compositeパターンと呼んでいます。Compositeパターンは、階層構造やツリー構造のオブジェクトを作りたいとき、そしてそのツリーを利用するコードが1つの単純なオブジェクトを扱っているのか、それともごちゃごちゃした枝全体を扱っているのかを考えさせたくないときに利用できます。

GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)

親となるオブジェクトのメソッドを呼び出すことで、子オブジェクトの同じメソッドを再帰的に呼び出すケースでよく使われます。会社の例だと、部の給料を求めるメソッドを呼び出すことで、課・人の給料を求めるメソッドを呼び出し、それを集計して値を返すイメージです。

Composite(コンポジット)とは、「複合的」という意味の単語です。

Compositeパターンには3つの要素によって構成されています。

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

サンプルコード

最近はもっぱらゴルフにハマっていて、ゴルフクラブセットが欲しいという気持ちが強いので、ゴルフクラブセットの金額を求めるプログラムを考えてみます。

ゴルフクラブセットは、ゴルフバッグと、ゴルフクラブ(10数本)で構成されています。さらにゴルフクラブは、ドライバー、アイアン(パターもあるけど今回は省略)で構成されています。

ゴルフクラブセットの金額を求めるには、それぞれの金額を全て足していく必要があります。

これをCompositeパターンで実装してみます。

クラス関係のイメージ図です。
クラスの関係図

# Componentクラス
class Part
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def price
    raise # インターフェイスとなるメソッドであり、呼び出されたらエラーを返すようにしている
  end
end

# Compositeクラスの基底クラス
class CompositePart < Part
  def initialize(name)
    super(name)
    @sub_parts = []
  end

  def add_sub_part(part)
    @sub_parts << part
  end

  def remove_sub_part(part)
    @sub_parts.delete(part)
  end

  def price
    @sub_parts.map(&:price).inject(:+) # 子クラスのpriceを集計
  end
end

# Compositeクラス
class ClubSet < CompositePart
  def initialize
    super('クラブセット')
    add_sub_part(Club.new)
    add_sub_part(GolfBag.new)
  end
end

# Leafクラス
class GolfBag < Part
  def initialize
    super('ゴルフバッグ')
  end

  def price
    10_000
  end
end

# Compositeクラス
class Club < CompositePart
  def initialize
    super('クラブ')
    add_sub_part(Driver.new)
    add_sub_part(Iron.new)
  end
end

# Leafクラス
class Driver < Part
  def initialize
    super('ドライバー')
  end

  def price
    30_000
  end
end

# Leafクラス
class Iron < Part
  def initialize
    super('アイアン')
  end

  def price
    15_000
  end
end

# -- 実行 --
p ClubSet.new.price # -> 55000

ClubSetクラスのインスタンスのpriceメソッドを呼ぶと、Compositeクラス・Leafクラスのpriceメソッドを呼んで金額を足していき、合計金額を出力できています。

ゴルフは楽しいけど、お金がかかりますね。

おわりに

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

個人的に前回までのデザインパターンよりもイメージが湧きづらく、少し自信がない部分もある(今回の記事はかなり時間がかかった...)ので、ここちょっと違うんじゃないの、であったり、ここをこうするともっと分かりやすいよ、みたいなことがあればお気軽にコメントいただけると嬉しいです。

Rubyによるデザインパターン の中では、様々な例を用いて説明がされていると共に、この記事には書ききれなかった注意事項などが色々と書かれているので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、イテレータパターンをまとめます。

来週も頑張ります!

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

【Rubyによるデザインパターンまとめ3】オブザーバーパターン

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

前回の記事(ストラテジーパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

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

今回はObserverパターンについてまとめました。
f:id:ysk_pro:20191110123459p:plain

Observerパターンとは

あるオブジェクトの状態が変化した時に、そのオブジェクトが変化したことを知る必要があるオブジェクトに通知をすデザインパターンです。

Rubyによるデザインパターン では次のように説明されていました。

GoFは「何らかのオブジェクトが変化した」というニュースの発信者と消費者の間に綺麗なインターフェイスを作るアイデアを、Observerパターンと呼んでいます。

GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)

自らが変化したことを通知するオブジェクトのことを「Subject」(話題になっている事柄)と呼び、Subjectの通知を受け取るオブジェクトのことを「Observer」(観察者)と呼ぶことから、Observerパターンと名付けられました。

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

サンプルコード

商品価格が変更された時に、様々な処理を行う必要があるプログラムを考えてみます。(ECサイトなどで実際によくあるケースだと思います)

まずは、商品価格が変わった時に、ユーザへの通知のみを行うプログラムを、Observerパターンを使わずに実装してみます。

class Item
  attr_reader :name, :price, :notification

  def initialize(name, price, notification)
    @name = name
    @price = price
    @notification = notification
  end

  def price=(new_price)
    @price = new_price
    notification.update(self) # 通知の処理は、Notificationクラスに委譲している
  end
end

class Notification
  def update(changed_item)
    puts "#{changed_item.name}の値段が#{changed_item.price}円になったよ!"
  end
end

商品価格が変わった時に呼ばれる price= メソッドの中で、Notificationクラスのupdateメソッドに通知処理を委譲しています。

次のように実行します。
Itemクラスのインスタンスを作る際に、Notificationクラスのインスタンスを渡す必要があります。

item = Item.new('ダンベル', 3000, Notification.new)
item.price = 2500

実行結果は、次のようになります。

ダンベルの値段が2500円になったよ!

ここで、価格変更をした時に、ユーザへの通知のみではなく、配送方法を変える処理も必要になった場合を考えてみましょう。

先ほどのコードに追加で、Itemクラスのインスタンス作成時に配送方法を変える処理を行うクラスのインスタンスを渡し、price= メソッド内で新しく作ったクラスに配送方法変更の処理を委譲することで実現できると思います。

しかしこのやり方だと、ItemクラスはNotificationクラスや新しく追加したクラスのことを知る必要があり(それぞれのクラスのどのメソッドを呼び出す必要があるかなど)、価格変更した後に行う処理を追加する度に、Itemクラスを修正する必要が出てきてしまいます。

商品の価格変更をした後に行うというだけで、直接商品とは関係ない処理を追加するにも関わらず Itemクラスの修正が必要になってしまうのは保守性の観点で良くないと言えるでしょう。(商品の価格変更した後の処理を追加する際に、Itemクラスを壊してしまうおそれがあります)

このコードを、Observerパターンを使って書き換えてみます。

class Item
  attr_reader :name, :price, :observers

  def initialize(name, price)
    @name = name
    @price = price
    @observers = []
  end

  def price=(new_price)
    @price = new_price
    notify_observers
  end

  def notify_observers
    observers.each do |observer|
      observer.update(self) # Observerそれぞれに処理を委譲している
    end
  end

  def add_observer(observer)
    observers << observer
  end
end

class Notification
  def update(changed_item)
    puts "#{changed_item.name}の値段が#{changed_item.price}円になったよ!"
  end
end

class DeliveryMethod
  def update(changed_item)
    puts "#{changed_item.name}の値段によって配送方法を分ける処理を書くよ"
  end
end

Itemクラスが自らに変化があったことを通知するSubjectクラスで、Notificationクラス・DeliveryMethodクラスが通知を受け取るObserverクラスになっています。

Itemクラスに @observers というインスタンス変数を作り、その中に価格変更後に通知を受け取るObserverクラスのインスタンスを格納することで、Itemクラスは @observers に価格変更があったことを通知することだけを知っていればよく、Notification, DeliveryMethod クラスについて知る必要がなくなりました。

次のように実行します。
add_observer メソッドで、observer を追加しています。

item = Item.new('ダンベル', 3000)
item.add_observer(Notification.new)
item.add_observer(DeliveryMethod.new)
item.price = 2500

実行結果は、次のようになります。

ダンベルの値段が2500円になったよ!
ダンベルの値段によって配送方法を分ける処理を書くよ

価格変更時に新たな処理を加えたい場合は、observerを追加するだけでよく、Itemクラスを修正する必要がなくなりました。

また、Rubyの標準ライブラリには、Observerパターンを実装するためのObservableモジュールがあり、このモジュールをSubjectクラスにincludeすることで、Observerパターンをシンプルに実装することができます。

先ほどのコードを、Observableモジュールを使って書き換えると次のようになります。

require 'observer'

class Item
  include Observable

  attr_reader :name, :price, :observers

  def initialize(name, price)
    @name = name
    @price = price
  end

  def price=(new_price)
    @price = new_price
    changed # changedメソッドを呼ぶことで、オブジェクトに変更があることを伝えている
    notify_observers(self)
  end
end

class Notification
  def update(changed_item)
    puts "#{changed_item.name}の値段が#{changed_item.price}になったよ!"
  end
end

class DeliveryMethod
  def update(changed_item)
    puts "#{changed_item.name}の値段によって配送方法を分ける処理を書くよ"
  end
end

先ほどと同じ呼び出し方で、同じ実行結果になります。

おわりに

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

前回書いたStrategyパターンの記事を読んでいただいた方は気づいたかもしれませんが、ObserverパターンはStrategyパターンと少し似ている気がするなぁと思っていたら、Rubyにおけるデザインパターンに次の記載がありました。

ObserverパターンとStrategyパターンは少し似ています。どちらも、あるオブジェクトが、他のオブジェクトを呼び出すという特徴があります。ほとんどの場合、違いは目的という一点だけです。Observerパターンの場合、発信側のオブジェクトで発生しているイベントを他のオブジェクトに伝えています。Strategyパターンの場合、何かの処理を行うためにオブジェクトを取得します。

TemplateメソッドパターンとStrategyパターンも同じ目的のために、違う手段で実装していましたが、デザインパターン同士で似ているところなどの関連があって面白いですね。
違いを認識しておくことで、それぞれのデザインパターンへの理解がより深まりそうです。

Rubyによるデザインパターン の中では、従業員の給与が変わった時に、経理部門や税務署員に通知するというサンプルコードを用いて説明がされていて非常に分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、Compositeパターンをまとめます。どんなパターンか楽しみです。

来週も頑張ります!

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

【Rubyによるデザインパターンまとめ2】ストラテジーパターン

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

前回の記事(テンプレートメソッドパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

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

今回はStrategyパターンについてまとめました。
f:id:ysk_pro:20191103103437p:plain

Strategyパターンとは

アルゴリズムの変化する部分をクラスに閉じ込めて、アルゴリズムを実行する際はそのクラスに処理を委譲するパターンです。

アルゴリズムに多様性を持たせたい場合に利用するのはTemplateメソッドパターンと同じですが、Strategyパターンは継承を使わずに実現することができます。(Templateメソッドパターンは継承を使っており、継承にはサブクラスがスーパークラスに依存してしまうというデメリットがあります)

Strategyパターンの「Strategy(戦略)」は、アルゴリズムのことです。Strategyパターンという名前は、変化するアルゴリズム(=Strategy)を、クラスに閉じ込めてそのアルゴリズムを使うオブジェクトに引き渡すことから付けられました。

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

サンプルコード

今回はスポーツネタでいってみます。

様々なスポーツの練習内容を出力するプログラムを考えてみます。(プログラム中にあるようにゴルフとバドミントンが好きでよくやっています)

Strategyパターンを用いて書いた例がこちらです。

class Sport
  attr_accessor :sport, :before_practice

  def initialize(sport)
    @sport = sport # Golfクラス・Badmintonクラスのインスタンスが入る
    @before_practice = 'ストレッチをする'
  end

  def practice
    sport.practice(self) # 詳細を知っている別のクラス(Golf,Badminton)のメソッドに処理を委譲している
  end
end

class Golf
  def practice(sport)
    puts sport.before_practice
    puts 'アイアンの練習'
    puts 'ドライバーの練習'
  end
end

class Badminton
  def practice(sport)
    puts sport.before_practice
    puts 'スマッシュの練習'
    puts 'ヘアピンの練習'
  end
end

Golfクラス・Badmintonクラスに、アルゴリズムの変化する部分を閉じ込めて、Sportクラスのpracticeメソッドの実行は、Golfクラス・Badmintonクラスに委譲しています。

こうすることによって、各スポーツの練習方法についての知識を、Sportクラスから分離することができます。

また、Sportクラスの情報(今回は スポーツ前にはストレッチをする という情報)をGolfクラス・Badmintonクラスに渡すために、practiceメソッドにSportクラス自身のインスタンスを引数として渡しています(before_practiceを引数として渡しても問題ありません)。

以下のように実行できます。
Sportクラスのインスタンス作成時の引数に、Golfクラス・Badmintonクラスのインスタンスを渡しています。

Sport.new(Golf.new).practice

Sport.new(Badminton.new).practice

実行結果は、それぞれこのようになります。

ストレッチをする
アイアンの練習
ドライバーの練習

ストレッチをする
スマッシュの練習
ヘアピンの練習


また、Procオブジェクトを利用すると、同じ内容を次のように書くこともできます。

class Sport
  attr_accessor :sport, :before_practice

  def initialize(&sport) # 引数としてブロックを受け取るので、&(アンパサンド)をつけている
    @sport = sport
    @before_practice = 'ストレッチをする'
  end

  def practice
    sport.call(self) # Procオブジェクトはcallメソッドで実行する
  end
end

こうすることで、アルゴリズムの変化する部分を閉じ込めるクラスを作る必要がなくなり(ブロックを疑似的なクラスとして扱っています)、よりシンプルにストラテジーパターンを利用することができるようになります。

次のように実行すると、先ほどと同じ実行結果となります。

Sport.new do |sport|
  puts sport.before_practice
  puts 'アイアンの練習'
  puts 'ドライバーの練習'
end.practice

Sport.new do |sport|
  puts sport.before_practice
  puts 'スマッシュの練習'
  puts 'ヘアピンの練習の練習'
end.practice

アルゴリズムの変化する部分が小さい場合は、Procクラスを使った方法の方がシンプルにStrategyパターンを実現できます。

おわりに

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

GoF(ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のこと)によれば、「継承よりも委譲の方がより柔軟で好ましい」とあるので、Templateメソッドを検討する際には、合わせてStrategyパターンも検討してみるのが良さそうです。

Rubyによるデザインパターン の中では、レポートをHTML形式やプレーンテキスト形式で出力するという実際にありそうなサンプルコードを用いて説明されていて非常に分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

やっぱりブログを書くと、理解が深まっていいですね。先日僕が実務で書いたコードを、早速 Templateパターン or Strategyパターンを使ってリファクタリングしたくなりました。

次回は、オブザーバーパターンをまとめます。どんなパターンか楽しみです。

来週も頑張ります!

(追記)
オブザーバーパターンについてまとめました!
Strategyパターンと似たところもあり、合わせて読むことでそれぞれの理解が深まると思います。
是非合わせてご覧ください。
ysk-pro.hatenablog.com