オブジェクト指向のポリモーフィズムについて今日は紹介するよ!
- オブジェクト指向のポリモーフィズムってなに?
- オブジェクト指向のポリモーフィズムは何のためにあるの?
- 説明だけだとわからないからコードで書きたい
などの疑問をお持ちの方の悩みを解決できる記事になっています!
オブジェクト指向は初心者の人にとって難しいかもしれません。色々なサイトで見たけど、よくわからなかったという人も多いのではないでしょうか。
オブジェクト指向の重要な概念で、次のようなものがあります。
- クラス
- カプセル化
- 継承
- ポリモーフィズム
オブジェクト指向を習得するためには、これらの理解が必要です。オブジェクト指向が扱う範囲は広いです。そのため、基本をしっかりと抑えておかないと、挫折してしまうかもしれません。
この記事では、オブジェクト指向のポリモーフィズム、ポリモーフィズムの目的、ポリモーフィズムの使い方などを完全初心者向けに解説します!
サンプルのコードをあわせて説明しますので、実際にコードを書いて動作確認してみましょう!
記事を読めば、オブジェクト指向のクラスについて理解ができますよ!
- オブジェクト指向のポリモーフィズムの使い方を知りたい人
- オブジェクト指向のポリモーフィズムをコードで書けるようになりたい人
- オブジェクト指向を一言で説明できない人
目次
オブジェクト指向のポリモーフィズムを学ぶ前に
オブジェクト指向のポリモーフィズムを学習する前に注意してほしいことがあります。
オブジェクト指向のポリモーフィズムを理解するために、実際にコードを書いて動作確認をしてください。
なぜなら、コードを書かずに頭だけで理解することは難しいからです。
サンプルコードを書いて動作確認することを「写経」といいます。
写経することで、ポリモーフィズムの書き方や、なぜ動くのかを確認することができます。初心者の頃は横着してコピペする人が多いですが、これは非常にもったいないです。
手を動かして書くことで、記憶の定着が上がり、コードを書くことに慣れます。
実際に、プロのエンジニアも新しいプログラミング言語を習うときは、写経する人が多いです。
写経することで、頭の整理がしやすいからですね。
それに、体で覚える感覚を養うことができます。
私も写経は必ずやります。
オブジェクト指向をしっかり理解したい人は、サンプルコードを書くようにしてくださいね。
それでは、見てみましょう!
Rubyの実行環境
この記事では、ポリモーフィズムのサンプルコードは「Ruby」を書きます。
Rubyの実行環境は手元のパソコンでもいいですし、オンライン上でも大丈夫です。
例えば、こちらのサイトならオンライン上でコードを書くことができます。
オブジェクト指向ってなに?
オブジェクト指向とは、システム開発を効率的に行うための総合的な技術を指します。
オブジェクト指向は当初はプログラミング言語として開発されました。「クラス」「継承」「ポリモーフィズム」などの概念を取り入れ、オブジェクト指向プログラミング言語(OOP)と呼ばれるようになりました。
その後、プログラミング以外にもデザインパターンやUML、モデリングなどにも応用され、オブジェクト指向という概念が形成されました。
オブジェクト指向の基本についてこちらの記事で詳しく解説しています。オブジェクト指向のポリモーフィズムを理解するためにも目を通しておきましょう。
オブジェクト指向のクラスとは
クラスとは「種類」「分類」を意味し、「性質が同じものの集まり」です。
Rubyでクラスを書くときは、class
キーワードを使います。
次の例では、車を表すCar
クラスを定義しています。
class Car
def initialize(name, color, model_year)
@name = name
@color = color
@model_year = model_year
end
attr_accessor :name, :color, :model_year
end
インスタンスは、new
を使って次のように書きます。
class Car
def initialize(name, color, model_year)
@name = name
@color = color
@model_year = model_year
end
attr_accessor :name, :color, :model_year
end
# インスタンスをつくる(インスタンス化させる)
toyota = Car.new("TOYOTA", "黒", "20年式")
puts toyota.name
puts toyota.color
puts toyota.model_year
クラスとインスタンスの違いや、クラスの使い方はこちらで解説しているので確認してみましょう。
オブジェクト指向のポリモーフィズムとは
オブジェクト指向のポリモーフィズムとは、「多態性」「さまざまな形に変わることができる」というような意味を持ちます。
「さまざまな形に変わることができる」とはどういうことでしょうか。
例えば、動物を例にしてみましょう。
ほとんどの動物は「鳴く」という動作をしますね。
犬、猫、鳥、ネズミ、カエルなど人も含めて「鳴く」ことができます。
しかし、同じ「鳴く」でもそれぞれ異なる音を出しますね。
犬なら「ワン」、猫なら「にゃー」、ネズミなら「チューチュー」など。
「鳴く」動作は同じだけど中身が違うこと、これを「多態性」、「ポリモーフィズム」といいます。
動物 | 動作 | 鳴き方 |
犬 | 鳴く | ワンワン |
猫 | にゃー | |
ネズミ | チューチュー | |
カエル | ゲコゲコ |
では、オブジェクト指向でポリモーフィズムを考えてみましょう。
オブジェクト指向でいうポリモーフィズムとは、「別々のクラスに共通のメソッドを持たせて、異なるメッセージを送ること」です。
動物の例で見てみましょう。
犬、猫、鳥、ネズミ、カエル、それぞれのクラスがあるとします。
これらのクラスはプログラムの中で「鳴く」動作をしなければなりません。
しかし、それぞれの「鳴き声」は違いますよね。
そのため、それぞれのクラスで「cry」という共通のメソッドを定義して、中身だけ変えます。
クラス | メソッド | メソッド内の動作 |
犬 | cry | ワンワン |
猫 | にゃー | |
ネズミ | チューチュー | |
カエル | ゲコゲコ |
このように定義することで、プログラムからそれぞれのクラスを利用するときは「cry」を呼び出せば、対象のクラスが犬だろうが、猫だろうが関係なく「鳴く」動作を実行できます。
このように「共通のメソッド(cry)を持たせて、異なるメッセージ(鳴き声)を送ること」で、コードをシンプルに保ち、拡張性の高い開発ができるのです。
説明だけだとイメージがつきづらいので、実際にコードを書いてみましょう!
ポリモーフィズムをRubyで書いてみる
ポリモーフィズムの基本を解説しました。
では、実際にポリモーフィズムをRubyで表現してみましょう。
ポリモーフィズムをコードで実現するためには、「インターフェース」という仕組みを使います。
インターフェースとは
インターフェースとは、クラスに特定のプロパティやメソッドを実装することを強制するための仕組みです。
インターフェースの定義はメソッドと似ていますが、メソッドと違い中の実装を記載しません。
Rubyでインターフェースを書くには次のようになります。
module Car
def run
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
Rubyでインターフェースを実現させるには、module
を使うと便利です。module
は特定のクラスに機能を提供できる仕組みです。
ここでは、Car
というスーパークラス(moduleですがクラスという扱いにしています)にrun
メソッドを定義しています。このCarクラスを継承したクラスではrun
メソッドを定義しないとエラーになります。
試しに、CarChild
というサブクラスを定義しましょう。
module Car
def run
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class CarChild
include Car
end
car1 = CarChild.new()
car1.run
これを実行すると、次のようにエラーを出力します。
スーパークラスのrun
メソッドがエラーを発生させて、CarChildにrun
メソッドを実装するように促しています。
では、CarChildにrun
メソッドを実装しましょう。
module Car
def run
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class CarChild
include Car
def run
puts "run!"
end
end
car1 = CarChild.new()
car1.run
実行結果は次の通りです。「run!」という文字が出力しているのが分かりますね。
このような仕組みを利用することで、CarChild
にrun
メソッドを実装するように強制させることができます。
インターフェースを使ってポリモーフィズムを実装する
では、インターフェースを利用してポリモーフィズムを実装しましょう。
実装するクラスは、先程の動物の例を使用します。
クラス | メソッド | メソッド内の動作 |
犬 | cry | ワンワン |
猫 | にゃー | |
ネズミ | チューチュー | |
カエル | ゲコゲコ |
まずは、スーパークラスとなるAnimal
クラスを書きます。Animal
クラスはインターフェースとなるcry
メソッドを定義します。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
このAnimalクラスを継承(この場合はincludeですが)したサブクラスはcry
メソッドを実装しなければなりません。
それでは、サブクラスの「犬、猫、ネズミ、カエル」を書きます。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
# サブクラスを定義する
class Dogs
end
class Cats
end
class Mouse
end
class Frog
end
ここに、Animal
をinclude
させて、cry
メソッドを定義します。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
# Animalをincludeし、cryメソッドを定義する
class Dogs
include Animal
def cry
puts "わんわん"
end
end
class Cats
include Animal
def cry
puts "にゃー"
end
end
class Mouse
include Animal
def cry
puts "ちゅーちゅー"
end
end
class Frog
include Animal
def cry
puts "ゲコゲコ"
end
end
これで全てのクラスでcry
メソッドを実装することができました。
では、次にSomaAnimal
というクラスを定義して、初期化の引数にインスタンスを受け取るように書きましょう。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class Dogs
include Animal
def cry
puts "わんわん"
end
end
class Cats
include Animal
def cry
puts "にゃー"
end
end
class Mouse
include Animal
def cry
puts "ちゅーちゅー"
end
end
class Frog
include Animal
def cry
puts "ゲコゲコ"
end
end
# SomeAnimalを定義し、インスタンスを引数で受け取る
class SomeAnimal
def initialize(animal)
@animal = animal
end
def call_cry
@animal.cry
end
end
これで、準備が整いました。
このSomeAnimal
クラスはcall_cry
というメソッドを持っています。call_cry
は初期化時に受け取ったオブジェクトからcry
メソッドを呼び出しています。
ここで先程作成した動物たちのクラスを当てはめてみましょう。
まずは、犬クラスをSomeAnimal
に渡してcall_cry
メソッドを呼び出します。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class Dogs
include Animal
def cry
puts "わんわん"
end
end
class Cats
include Animal
def cry
puts "にゃー"
end
end
class Mouse
include Animal
def cry
puts "ちゅーちゅー"
end
end
class Frog
include Animal
def cry
puts "ゲコゲコ"
end
end
class SomeAnimal
def initialize(animal)
@animal = animal
end
def call_cry
@animal.cry
end
end
# 犬クラスのインスタンスを渡して、call_cryを呼び出す
animal1 = SomeAnimal.new(Dogs.new())
animal1.call_cry
実行結果は次の通り、「わんわん」と出力されました。
なぜ、call_cry
を実行したら「わんわん」と出たのでしょうか。
それは、SomeAnimal
をインスタンス化するときに犬クラスのインスタンスを渡してプロパティにセットしているからです。
# 犬クラスのインスタンスを渡して、call_cryを呼び出す
animal1 = SomeAnimal.new(Dogs.new())
ここでSomeAnimal
のinitialize
で犬クラスのインスタンスがセットされます。
class SomeAnimal
def initialize(animal)
# 犬クラスのインスタンスがここにくる
@animal = animal
end
def call_cry
@animal.cry
end
end
そのため、@animal.cry
の中身はDogs.new().cry
と等しいことになります。
なので、「わんわん」と出力されます。
それでは、他の動物たちも同じように実装してみましょう。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class Dogs
include Animal
def cry
puts "わんわん"
end
end
class Cats
include Animal
def cry
puts "にゃー"
end
end
class Mouse
include Animal
def cry
puts "ちゅーちゅー"
end
end
class Frog
include Animal
def cry
puts "ゲコゲコ"
end
end
class SomeAnimal
def initialize(animal)
@animal = animal
end
def call_cry
@animal.cry
end
end
animal1 = SomeAnimal.new(Dogs.new())
animal1.call_cry
# 他の動物たちも同じように呼び出す
animal2 = SomeAnimal.new(Cats.new())
animal2.call_cry
animal3 = SomeAnimal.new(Mouse.new())
animal3.call_cry
animal4 = SomeAnimal.new(Frog.new())
animal4.call_cry
実行結果は次のようになります。
それぞれの動物だちのcry
メソッドが呼び出されて、異なる鳴き声を出しているのが分かりますね。
これが、オブジェクト指向のポリモーフィズムの使い方です。
オブジェクト指向のポリモーフィズムの定義をもう一度確認してみましょう。
- ポリモーフィズムの意味は「多態性」「さまざまな形に変わることができる」こと
- オブジェクト指向のポリモーフィズムは、別々のクラスに共通のメソッドを持たせて、異なるメッセージを送ること
でしたね。
実際のコードを見ると、意味を理解できたのではないでしょうか。
共通のメソッドが「cry
」です。そして、異なるメッセージをそれぞれのクラスが送っていますよね。
オブジェクト指向のポリモーフィズムは、このようにしてクラスに共通のメソッドを持たせて、中身だけ異なるように実装することができます。
なぜ、ポリモーフィズムが必要なのか
では、なぜポリモーフィズムのような仕組みが必要なのでしょうか。
ポリモーフィズムが必要な理由は次の通りです。
- さまざまなパターンに対応できるため
- インターフェースを通すことで変更に強くなるため
さまざまなパターンに対応できるため
オブジェクト指向のポリモーフィズムと使うと、さまざまなパターンに対応できます。
例えば、先程の動物の例だと「犬、猫、ねずみ、カエル」だけでしたが、鳴くことができる動物なら全て対応可能です。パンダやアシカ、ライオンなど色々な動物たちを追加できますよね。
実際のビジネスの現場では、このようなことが頻繁におきます。同じような処理内容なんだけど、Aのパターンはこうで、Bのパターンはこう。同じことをしているのに、状況によって処理の内容が変わるのです。
オブジェクト指向のポリモーフィズムは、このような複雑なビジネス要件を解決できる手助けになるのです。
インターフェースを通すことで変更に強くなるため
ポリモーフィズムを使うと変更に強いコードになります。
ポリモーフィズムを使うときは、インターフェースを実装します。
これは、JavaやPHPといった他のプログラミング言語でも同様です。
先程の例では、cry
というメソッドをインターフェースにして定義しました。そして、それぞれの動物クラスがcryメソッドの内部を変えて実装しています。
もし、犬クラスの鳴き声だけを変えたかったら、次のように変更します。
module Animal
def cry
raise NotImplementedError.new("#{self.class}##{__method__} が実装されていません")
end
end
class Dogs
include Animal
def cry
# 鳴き声を変更する
puts "ふぉふぉふぉ"
end
end
class Cats
include Animal
def cry
puts "にゃー"
end
end
class Mouse
include Animal
def cry
puts "ちゅーちゅー"
end
end
class Frog
include Animal
def cry
puts "ゲコゲコ"
end
end
class SomeAnimal
def initialize(animal)
@animal = animal
end
def call_cry
@animal.cry
end
end
animal1 = SomeAnimal.new(Dogs.new())
animal1.call_cry
animal2 = SomeAnimal.new(Cats.new())
animal2.call_cry
animal3 = SomeAnimal.new(Mouse.new())
animal3.call_cry
animal4 = SomeAnimal.new(Frog.new())
animal4.call_cry
変更があった箇所を注目してください。Dogs
クラスのcry
メソッドの中身だけが変更されていますよね。
その他は変更されていません。
呼び出し元のSomeAnimal
や、call_cry
を書き換えなくても仕様を変更することができました。
プログラミングでは、なるべく影響範囲を小さくして、変更のしやすいコードが良いとされています。
ポリモーフィズムを使えば、このように変更に強く、拡張性の高いコードを保つことができるのです。
まとめ
この記事では、オブジェクト指向のポリモーフィズムについてまとめました。
最後にもう一度内容を確認しましょう。
- ポリモーフィズムの意味は「多態性」「さまざまな形に変わることができる」こと
- オブジェクト指向のポリモーフィズムは、別々のクラスに共通のメソッドを持たせて、異なるメッセージを送ること
- ポリモーフィズムが必要なのは、「さまざまなパターンに対応するため」「変更に強いコードを保つため」
オブジェクト指向は難しい概念ですが、基礎を理解し、実践すれば身に付きます!
オブジェクト指向のポリモーフィズム以外にも、クラス、継承、カプセル化も重要な概念です。
こちらもあわせてしっかり確認しておきましょう!