こんにちは。
最近 リファクタリング Rubyエディション を読みました。本書は、復刻版が最近発売された名著でリファクタリングの様々なパターンをRubyのサンプルコードを使って解説しています。
紹介されているリファクタリングのパターン数は膨大で全てをまとめるのは難しいので、いいなと思ったパターンを1つずつブログにまとめています。
今回はその第3弾で「継承から委譲へ」についてまとめます。
オリジナルの Ruby のサンプルコードを使いながら説明しています。
第2弾の「モジュールの抽出」のまとめはこちらです。
モジュールの抽出【リファクタリング Rubyエディションまとめ2】 - 銀行員からのRailsエンジニア
「継承から委譲へ」とは
概要
継承を使っていた箇所を、継承の関係を解消して委譲を使うようにするリファクタリングです。
どんな時に使えるか
継承関係にあるものの、サブクラスがスーパークラスのインターフェースの一部しか使っていなかったり、スーパークラスのメソッドの多くがサブクラスにうまくかみ合っていない時に有効となります。
メリット
不要なメソッドを継承することがなくなり、予期せぬ副作用を防ぎつつコードの共通化を維持することができます。
サンプルコード
リファクタリング前
サンプルコードを見ていきましょう。
こちらはリファクタリング前のコードです。
class Tasks < Array def finish(task) puts 'Well done!' delete(task) end def list each { |task| puts task } end end
Array クラスを継承した Tasks クラスを作ってみました。
tasks = Tasks.new tasks.push('clean room') tasks.push('study ruby') tasks.list tasks.finish('clean room') tasks.list
上記のように実行すると、下記の結果になります。
clean room study ruby Well done! study ruby
Array クラスを継承してしていますが、Array クラスの一部のメソッド(push, delete, each)しか使っていませんね。
Tasks クラスに必要なメソッドのみを使えるよう、委譲を使ってリファクタリングしてみましょう。
リファクタリング後
「継承から委譲へ」を使ってリファクタリングすると次のようになります。
require 'forwardable' class Tasks extend Forwardable def_delegators :@tasks, :each, :delete, :push def initialize @tasks = [] # Array.new でも同じ end def finish(task) puts 'Well done!' delete(task) end def list each { |task| puts task } end end
実行方法、結果はリファクタリング前と同じになります。
Tasks クラスは Array クラスを継承しなくてよくなり、Array クラスで初期化したインスタンス変数である @tasks に処理を委譲することで、Array クラスのメソッドを利用できています。
これで不要なメソッドの継承は防ぎつつ、利用したい Array クラスのメソッドを使えるようになりました。
委譲の部分は、Forwardable モジュールの def_delegators メソッドを使うことで実現しています。
おわりに
ここまでお読みいただきありがとうございます。
継承関係がいいのか、委譲を使った方がいいのかは難しく、コードの増加とともに変更したくなるケースもあると思うので、この方法は覚えておくと役立ちそうですね。
リファクタリング Rubyエディションでは、Hashクラスを継承しているサンプルコードで丁寧に解説されていてとても分かりやすかったので、ご興味ある方は是非ご覧ください。
- 作者:ジェイ・フィールズ,シェーン・ハービー,マーティン・ファウラー
- 発売日: 2020/03/21
- メディア: 単行本
次回はまた違うパターンをまとめていきます!
(追記)
第4弾として「nullオブジェクトの導入」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com