Rubyの継承を徹底解説!今日からOOPの基本をマスターしよう
クラスの継承は、オブジェクト指向プログラミング(OOP)における基本的な機能の一つです。既存のクラスをベースに、より具体的で特殊化されたクラスを作り出すことができます。
継承の基本
まずは例を見てみましょう:
Food -> Fruit -> Orange
これらのクラスには明確な関係があります。「オレンジは果物であり、果物は食べ物である」と表現できますね。
親クラス(スーパークラスや基底クラスとも呼ばれます)は、常にサブクラスよりも一般的な存在です。Fruit(より一般的)は、Orange(より具体的)の親クラスにあたります。
Rubyでは次のように書きます:
class Food end class Fruit < Food end class Orange < Fruit end
Rubyにおける継承の重要なポイントは、Foodで定義されたすべてのメソッドと定数が、FruitやOrangeでも利用できるようになることです。メソッドは基底クラスからサブクラスへと受け継がれますが、その逆はありません。
継承の目的は何?
「オブジェクトの階層を作れば、コードがきれいに整理されて気持ちいい」と思うかもしれませんが、継承にはもっと実用的な目的があります。
継承は、特定の用途に合わせて親クラスの別バージョンを作るために使われます。例えば、親クラスにはそぐわない追加機能やメソッドが必要になるケースなどです。
例を挙げましょう:
すべての果物には「色」「重さ」「名前」といった属性があります。しかし、一部の果物には他の果物と共有されない特別な特徴があるかもしれません。そんなときは、すべての果物に共通する特徴(色や重さなど)を継承する新しいクラスを作り、そこに特殊な特徴を追加すればよいのです。
これこそが「特殊化」の意味するところです。
もう一つの例:
データベースに書き込むクラスがあり、デバッグ用にすべてのデータベース操作をログに記録する別バージョンが欲しいとしましょう。このような場合はデコレータパターンが有効です。継承を使って既存のクラスをラップし、元のクラスを一切変更することなく新しい機能を追加できます。
現実世界での継承
さて、継承について学びましたが、Ruby開発者としてあなたは毎日継承を使っていることをご存知でしたか?
Ruby自体が継承によって、以下のようなメソッドを提供しています:
putsclasssuper
これは、すべてのRubyオブジェクトがデフォルトでObjectクラスを継承しているからです。
たとえば、次のようなクラスを作ると:
class Apple end
その親クラスはObjectになります:
Apple.superclass # Object
だからこそ、先ほど挙げたようなメソッドが使えるわけです。
たとえばputsを呼び出すと、Rubyはまず自分のクラス内でこのメソッドを探します。
その後の流れはこうです:
- 親クラスの中からメソッドを探す
- 見つからなければ、再びオブジェクト側から
method_missingを探し始める - それでも見つからなければ
NoMethodErrorが発生します。明示的なオブジェクトなしでメソッドを呼んだ場合(a.sizeではなくsizeのように)はNameErrorになります
Railsにも継承の実例がたくさんあります。
たとえばこちら:
class ApplicationController < ActionController::Base end
コントローラ:
class SessionsController < ApplicationController end
モデル:
class Comment < ApplicationRecord belongs_to :article end
このようにRubyでは継承が至る所で使われていますが、必ずしも最適な解決策とは限りません。
継承の代替手段:コンポジション
継承にはいくつかの限界があります。
例えば:
パーツからコンピュータを組み立てたいとします。コンピュータはパーツを「持って」いますが、個々のパーツそれ自体はコンピュータではありません。バラバラにしてしまえば、それぞれの機能を果たせなくなります。
ここで必要なのは…
コンポジション(合成)です!
コンポジションは、異なるパーツが組み合わさって一つの機能を実現するクラスを作ります。まさにコンピュータのようなイメージです。
コンポジションの実際の例:
class Computer
def initialize(memory, disk, cpu)
@memory = memory
@disk = disk
@cpu = cpu
end
end
コンピュータは、動作に必要なパーツを受け取ります。これがコンポジションです。
リスコフの置換原則(LSP)
継承は適切な場面で使えば非常に強力ですが、どんなツールでも乱用は禁物です!
実際、古典的名著『Design Patterns』には、こんな有名な言葉があります:
「継承よりも合成を優先せよ。」
Design Patterns: Elements of Reusable Object-Oriented Software
継承を正しく使うために守れる原則があります。それはSOLID原則のL、「リスコフの置換原則」です。
この原則は「サブクラスは、基底クラスの代わりとして使えなければならない」というものです。
言い換えると:
Fruitを継承していて、colorメソッドが文字列を返す設計なら、サブクラスでcolorがシンボルを返すように変更してはいけません。
例:
Fruitの利用者は、colorが文字列を返すことに依存しています。
class Fruit
def color
"orange"
end
end
これはLSPに違反します:
class Orange < Fruit
def color
:orange
end
end
colorがシンボルを返すように変更すると、FruitオブジェクトをOrangeで置き換えられなくなります。なぜでしょうか? シンボルにはsplitのようなメソッドが存在しないからです。文字列にはあるのに、シンボルにはありません。
もう一つの危険信号は、サブクラスが親クラスの真の特殊化になっておらず、単にユーティリティメソッドを共有するためだけに親クラスを利用しているケースです。
まとめ
Rubyにおける継承とコンポジションについて学びました!
継承は親クラスを特殊化したバージョンを作るために、コンポジションは部品を組み合わせて全体を構築するために使いましょう。そして、コードをぐちゃぐちゃにしないために、LSPの原則を忘れないでください。
これでもっといいオブジェクト指向のコードが書けるようになりますよ 🙂
最後まで読んでいただきありがとうございました。
-
【保存版】「凝集度」と「結合度」でRubyコードはここまで変わる!基本のOOP原則を実例で解説
オブジェクト指向設計において最も重要な原則のひとつが、凝集度(Cohesion)と結合度(Coupling)です。この2つの概念を理解すると、コードベース内のすべてのクラスを特定のレンズを通して評価できるようになり、クラス設計の堅牢さや改善すべき箇所が明確に見えてきます。 凝集度と結合度とは何か? 凝集度とは、クラス内にあるメソッド同士の関係性を表す概念です。各メソッドが同じインスタンス変数や引数を使い、共通の目的に向かって協力し合っているか?それとも、メソッドごとに役割がバラバラで、互いに無関係に感じられるでしょうか? 結合度とは、あるクラスが他のクラスにどれほど依存しているか、システムの他
-
【Ruby】デコレータデザインパターンでクラスを変更せずにオブジェクトを拡張する方法
デコレータ(Decorator)デザインパターンとは、どのようなものなのでしょうか? また、このパターンをRubyのプロジェクトではどのように活用すればよいのでしょうか? デコレータデザインパターンを利用すると、クラスそのものを変更することなく、オブジェクトに新しい機能を追加して拡張することができます。 それでは、具体的な例を見ていきましょう! ログ出力とパフォーマンス計測の実装例 この例では、rest-clientのようなGemを使ってHTTPリクエストを送信します。 コードは次のようになります。 require restclient data = RestClient.get(www.