コードの品質向上のため、Rubyでデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第7弾です。(毎週1つが目標です!)
前回の記事(コマンドパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com
今回は アダプター(Adapter)パターン についてまとめました。
新しい試みとして、Rails の ActiveRecord の中でアダプターパターンが使われている箇所があったので、その箇所をコードリーディングしながら説明しています。
アダプターパターンとは
必要なインターフェースと既存のオブジェクトのインターフェースの違いを吸収するデザインパターンです。
以下の「アダプタ」という言葉の説明そのままの役割です。
アダプタとは、異なる複数の機器に接続する際に用いられる中間装置の総称である。(IT用語辞典バイナリ より)
文章の説明よりも実際のコードを見た方が分かりやすいと思うので、以下のコードをご覧ください。
アダプターパターンが実際に使われているコード
今回は、ActiveRecord 内でアダプターパターンが実際に使われている箇所のコードリーディングを行います。(ActiveRecord は、Ruby on Rails で使われる O/R マッパーです)
その前にまず、アダプターパターンを使っている理由を説明します。
MySQL、Postgresなどのデータベースはそれぞれ Ruby API を提供していますが、それぞれの API のインターフェースはそれぞれ異なっています。
例えば、MySQLのデータベースへ接続して何らかのSQLを実行したい場合は、MySQLのAPIの仕様上 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パターンは、Rails の ActiveRecord というかなり身近なところで使われており面白いですね。
Rubyによるデザインパターン の中では、ActiveRecordの例以外にも様々なサンプルコードを用いて説明されており分かりやすかったので、ご興味ある方は是非合わせてご覧ください。
- 作者:Russ Olsen,ラス・オルセン
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
次回は、プロキシパターンをまとめます。
来週も頑張ります!
(追記)
プロキシパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com