Savvy Code

オブジェクト指向のポリモーフィズムとは?【サンプルコード付きで解説!】

オブジェクト指向のポリモーフィズムについて今日は紹介するよ!

  • オブジェクト指向のポリモーフィズムってなに?
  • オブジェクト指向のポリモーフィズムは何のためにあるの?
  • 説明だけだとわからないからコードで書きたい

などの疑問をお持ちの方の悩みを解決できる記事になっています!

オブジェクト指向は初心者の人にとって難しいかもしれません。色々なサイトで見たけど、よくわからなかったという人も多いのではないでしょうか。

オブジェクト指向の重要な概念で、次のようなものがあります。

  • クラス
  • カプセル化
  • 継承
  • ポリモーフィズム

オブジェクト指向を習得するためには、これらの理解が必要です。オブジェクト指向が扱う範囲は広いです。そのため、基本をしっかりと抑えておかないと、挫折してしまうかもしれません。

この記事では、オブジェクト指向のポリモーフィズム、ポリモーフィズムの目的、ポリモーフィズムの使い方などを完全初心者向けに解説します!

サンプルのコードをあわせて説明しますので、実際にコードを書いて動作確認してみましょう!

記事を読めば、オブジェクト指向のクラスについて理解ができますよ!

こんな人におすすめ
  • オブジェクト指向のポリモーフィズムの使い方を知りたい人
  • オブジェクト指向のポリモーフィズムをコードで書けるようになりたい人
  • オブジェクト指向を一言で説明できない人

オブジェクト指向のポリモーフィズムを学ぶ前に

オブジェクト指向のポリモーフィズムを学習する前に注意してほしいことがあります。

オブジェクト指向のポリモーフィズムを理解するために、実際にコードを書いて動作確認をしてください。

なぜなら、コードを書かずに頭だけで理解することは難しいからです。

サンプルコードを書いて動作確認することを「写経」といいます。

写経することで、ポリモーフィズムの書き方や、なぜ動くのかを確認することができます。初心者の頃は横着してコピペする人が多いですが、これは非常にもったいないです。

手を動かして書くことで、記憶の定着が上がり、コードを書くことに慣れます。

実際に、プロのエンジニアも新しいプログラミング言語を習うときは、写経する人が多いです。

写経することで、頭の整理がしやすいからですね。

それに、体で覚える感覚を養うことができます。

私も写経は必ずやります。

オブジェクト指向をしっかり理解したい人は、サンプルコードを書くようにしてくださいね。

それでは、見てみましょう!

プログラミング言語って何?
そもそも「プログラミング」がよくわからないという人はこちらもあわせてご確認ください。 プログラミングとは?初心者向けにわかりやすく解説!

Rubyの実行環境

この記事では、ポリモーフィズムのサンプルコードは「Ruby」を書きます。

Rubyの実行環境は手元のパソコンでもいいですし、オンライン上でも大丈夫です。

例えば、こちらのサイトならオンライン上でコードを書くことができます。

Execute Ruby Online

オブジェクト指向ってなに?

オブジェクト指向とは、システム開発を効率的に行うための総合的な技術を指します。

オブジェクト指向は当初はプログラミング言語として開発されました。「クラス」「継承」「ポリモーフィズム」などの概念を取り入れ、オブジェクト指向プログラミング言語(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!」という文字が出力しているのが分かりますね。

このような仕組みを利用することで、CarChildrunメソッドを実装するように強制させることができます。

インターフェースを使ってポリモーフィズムを実装する

では、インターフェースを利用してポリモーフィズムを実装しましょう。

実装するクラスは、先程の動物の例を使用します。

クラス メソッド メソッド内の動作
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

ここに、Animalincludeさせて、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())

ここでSomeAnimalinitializeで犬クラスのインスタンスがセットされます。

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を書き換えなくても仕様を変更することができました。

プログラミングでは、なるべく影響範囲を小さくして、変更のしやすいコードが良いとされています。

ポリモーフィズムを使えば、このように変更に強く、拡張性の高いコードを保つことができるのです。

まとめ

この記事では、オブジェクト指向のポリモーフィズムについてまとめました。

最後にもう一度内容を確認しましょう。

  • ポリモーフィズムの意味は「多態性」「さまざまな形に変わることができる」こと
  • オブジェクト指向のポリモーフィズムは、別々のクラスに共通のメソッドを持たせて、異なるメッセージを送ること
  • ポリモーフィズムが必要なのは、「さまざまなパターンに対応するため」「変更に強いコードを保つため」

オブジェクト指向は難しい概念ですが、基礎を理解し、実践すれば身に付きます!

オブジェクト指向のポリモーフィズム以外にも、クラス、継承、カプセル化も重要な概念です。

こちらもあわせてしっかり確認しておきましょう!

オブジェクト指向のクラスとは?初心者でもわかる! オブジェクト指向の継承とは?初心者向けに解説!【サンプルコード付き】 オブジェクト指向のカプセル化とは?初心者向けに分かりやすく解説!【サンプルコードあり】