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

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

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

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

【Rubyによるデザインパターンまとめ8】プロキシパターン

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

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

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

今回は プロキシ(Proxy)パターン についてまとめました。

f:id:ysk_pro:20191229171915p:plain

プロキシパターンとは

本来呼び出したいオブジェクトとの間にオブジェクトを挟むことで、様々な処理を差し込むことができる デザインパターンです。

言い換えると、本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出し、別のオブジェクトに様々な処理を追加することができます。

インターフェースは、本来呼び出したいオブジェクトと同じにします。

よく使用されるケースとしては、アクセス制御生成コストのかかるオブジェクトのインスタンス化遅延 などがあり、これらをプロキシ(挟んだオブジェクト)側で実装することで、関心事を分離することができます。(アクセス制御などが本来呼び出したいオブジェクトの関心事ではない場合に、それをプロキシ側に切り出すことができます)

プロキシ(Proxy)は「代理」という意味であり、中継サーバである「プロキシサーバ」と同じイメージです。

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

サンプルコード

商品の購入、商品へのコメントができるプログラムを考えてみます。

class Item
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def buy
    puts "商品「#{name}」を購入するよ"
  end

  def comment
    puts "商品「#{name}」にコメントするよ"
  end
end

商品の購入・コメントは、次のように実行できます。

item = Item.new('MacBook Air')
item.buy
item.comment

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

商品「MacBook Air」を購入するよ
商品「MacBook Air」にコメントするよ

このコードをプロキシパターンを使って、ログインユーザーのみ 購入・コメントができるようにしてみましょう。(プロキシパターンとは のところで触れた アクセス制御 の例です)

class ItemProxy
  attr_reader :item, :user_name

  def initialize(item, user_name = nil)
    @item = item
    @user_name = user_name
  end

  def buy
    check_login
    item.buy
  end

  def comment
    check_login
    item.comment
  end

  private

  def check_login
    raise 'ログインしてください' unless login?
  end

  def login?
    user_name == 'ログインユーザー' # 本来はここでログインユーザーかどうか確かめるロジックを書く
  end
end

ItemProxyクラスをItemクラスとの間に挟み、ログインユーザーかどうかを確かめる処理を入れています。
次のように、ログインしていないユーザーで実行するとエラーとなります。

proxy = ItemProxy.new(item)
proxy.buy
proxy.comment

次のように、ログインユーザーで実行するとエラーは発生せず、先ほどと同じ実行結果となります。

proxy2 = ItemProxy.new(item, 'ログインユーザー')
proxy2.buy
proxy2.comment

こうすることによって、Itemクラスの関心事ではない ログインしているかどうか についてをItemクラスに書かなくてもよくなりました。


さらにここで、method_missingメソッド を使ってリファクタリングしてみましょう。
method_missingメソッドは、呼び出したメソッドが存在しないときに呼び出されるメソッドです。

class ItemProxy
  attr_reader :item, :user_name

  def initialize(item, user_name = nil)
    @item = item
    @user_name = user_name
  end

  def method_missing(name, *args)
    check_login
    item.send(name, *args)
  end

  private

  def check_login
    raise 'ログインしてください' unless login?
  end

  def login?
    user_name == 'ログインユーザー' # 本来はここでログインユーザーかどうか確かめるロジックを書く
  end
end

実行方法・実行結果は先ほどと同じになります。

method_missingメソッドを書くことで、ItemProxyクラスにbuyメソッドとcommentメソッドを記載する必要がなくなりました。
こうすることで、Itemクラスのメソッドを増やした場合に ItemProxyクラスの修正が不要になります。

アダプターパターンとの違い

アダプターパターンは、オブジェクトのインターフェースを変換するためにそのオブジェクトをラップするパターンです。

(アダプターについての記事はこちら)
【Rubyによるデザインパターンまとめ7】アダプターパターン - 銀行員からのRailsエンジニア

どちらも本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出しているところが似ておりますが、以下の点で異なります。

  • アダプターパターンは、オブジェクトのインターフェースを変換する
  • プロキシパターンは、インターフェースは変えずにアクセス制御などの処理を追加する

おわりに

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

この記事では、プロキシパターンのよくある使い方の1つである「アクセス制御」の説明しかできませんでしたが、Rubyによるデザインパターン の中では他の使い方の解説もされているので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、デコレータ(Decorator)パターンをまとめます。

来週も頑張ります!

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

【Rubyによるデザインパターンまとめ7】アダプターパターン

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

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

今回は アダプター(Adapter)パターン についてまとめました。

新しい試みとして、RailsActiveRecord の中でアダプターパターンが使われている箇所があったので、その箇所をコードリーディングしながら説明しています。

f:id:ysk_pro:20191222141612p:plain

アダプターパターンとは

必要なインターフェースと既存のオブジェクトのインターフェースの違いを吸収するデザインパターンです。

以下の「アダプタ」という言葉の説明そのままの役割です。

アダプタとは、異なる複数の機器に接続する際に用いられる中間装置の総称である。(IT用語辞典バイナリ より)

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

アダプターパターンが実際に使われているコード

今回は、ActiveRecord 内でアダプターパターンが実際に使われている箇所のコードリーディングを行います。(ActiveRecord は、Ruby on Rails で使われる O/R マッパーです)

その前にまず、アダプターパターンを使っている理由を説明します。

MySQL、Postgresなどのデータベースはそれぞれ Ruby API を提供していますが、それぞれの API のインターフェースはそれぞれ異なっています。

例えば、MySQLのデータベースへ接続して何らかのSQLを実行したい場合は、MySQLAPIの仕様上 query メソッドを呼ぶ必要があります。

results = mysql_connection.query(sql)

一方で、Sybase というデータベースへ接続している場合は、同じ操作をするには sql メソッドを呼ぶ必要があります。

results = sybase_connection.sql(sql)


同じ操作をするのに、データベースの種類毎に変わるメソッドを覚えて使い分けるのは大変ですよね。

そこで、ActiveRecord はデータベースの種類を意識しなくても操作が行えるように、データベース毎の違いを吸収したインターフェース(AbstractAdapterクラス)を提供しています。(まさにアダプターパターンですね!)

AbstractAdapter で定義されているメソッドの1つであり、SQLのSELECT文の結果を返す select_all メソッドを例にRailsのコードを見ていきましょう。

(以下全てのコードは、2019/12/21時点の Rails/ActiveRecord のコードの関係ある部分の抜粋です)

AbstractAdapter は DatabaseStatements を include しており、

module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter
      include DatabaseStatements
    end
  end
end

参考:GitHubへのリンク

DatabaseStatements の中で select_all メソッドを定義しています。これが統一したインターフェースです。

module ActiveRecord
  module ConnectionAdapters
    module DatabaseStatements

      def select_all(arel, name = nil, binds = [], preparable: nil)
        arel = arel_from_relation(arel)
        sql, binds = to_sql_and_binds(arel, binds)

        if preparable.nil?
          preparable = prepared_statements ? visitor.preparable : false
        end

        if prepared_statements && preparable
          select_prepared(sql, name, binds)
        else
          select(sql, name, binds)
        end
      end
    end
  end
end

参考:GitHubへのリンク

次に、AbstractAdapter を継承して、データベース毎にそのデータベースのAPIを使って select_all メソッドを実装している箇所を見ていきます。MySQLを例に該当箇所を見ていきましょう。

AbstractMysqlAdapter が AbstractAdapter を継承しており、

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
    end
  end
end

参考:GitHubへのリンク

AbstractMysqlAdapter を継承した Mysql2Adapter で MySQL::DatabaseStatements を include しており、

module ActiveRecord
  module ConnectionAdapters
    class Mysql2Adapter < AbstractMysqlAdapter
      include MySQL::DatabaseStatements
    end
  end
end

参考:GitHubへのリンク

MySQL::DatabaseStatements で select_all メソッドが定義されていました。

module ActiveRecord
  module ConnectionAdapters
    module MySQL
      module DatabaseStatements
        def select_all(*, **)
          result = if ExplainRegistry.collect? && prepared_statements
                     unprepared_statement { super }
                   else
                     super
                   end
          @connection.abandon_results!
          result
        end
      end
    end
  end
end

参考:GitHubへのリンク

おわりに

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

Adapterパターンは、RailsActiveRecord というかなり身近なところで使われており面白いですね。

Rubyによるデザインパターン の中では、ActiveRecordの例以外にも様々なサンプルコードを用いて説明されており分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、プロキシパターンをまとめます。

来週も頑張ります!

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

【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

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

来週も頑張ります!

(追記)
アダプターパターンについてまとめました!
是非合わせてご覧ください。
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の資格取得時に書いたブログも、よければ合わせてご覧ください。

ysk-pro.hatenablog.com

 

NIKOTAMA.rbで発表してきました(2019/12/19 追記)

第8回 NIKOTAMA.rb で「AWS re:Invent 2019 に行ってきました!」という発表をしました。

re:Invent で発表されたサービスや現地で体験したこと、感想などをまとめているのでこちら↓のスライドも是非合わせてご覧くださいー!

【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によるデザインパターン

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

来週も頑張ります!

(追記)
コマンドパターンについてまとめました!
是非合わせてご覧ください。
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