14
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初学者向け】 多態性って何が嬉しいの?

Last updated at Posted at 2025-03-24

概要

皆さんは「多態性」とは何かを説明できますか?
約2年前にエンジニアデビューを果たした私は、当時このように感じていました。

「多態性の説明はなんとなくわかるんだけど、なんでこんな複雑な書き方するんだろう...」

多態性を利用したコードを理解することはできるのですが、いまいち何が嬉しいのかわからずに学習を進めていたように思います。

本記事は、初学者に向けて多態性とは何か?から始め、多態性を利用するうまみやメリットを中心に解説していきます。初学者の方にとって少しでも学習の助けやモチベーションになれば幸いです。

なお、本記事内に登場するサンプルコードは全てRubyで書かれています。

多態性(ポリモーフィズム)とは?

多態性とは何か?と手っ取り早く言い表すならば、
「同一名称のメソッドがクラスによって異なるふるまいを持つ」
ということです。よくわからないので早速次の章から例を見ていきたいとおもいます。

例を挙げてみる

以下のように、Dogクラス・Catクラス・Birdクラスを用意します。
各クラスは、Animalクラスのサブクラスであり、greet(あいさつする)というメソッドのみを持っています。

class Animal
    def greet
        raise NotImplementedError
    end
end


class Dog < Animal
    def greet
        puts 'ワン!'
    end
end

class Cat < Animal
    def greet
        puts 'にゃー'
    end
end

class Bird < Animal
    def greet
        puts 'ちゅん!'
    end
end

クラス図にすると以下のようになります。

この時、以下の3つのgreetメソッドは何を出力するでしょうか。

animal = Dog.new
animal.greet
animal = Cat.new
animal.greet
animal = Bird.new
animal.greet

わかりましたか?
出力はそれぞれ以下のようになります。

# => 'ワン!'
# => 'にゃー'
# => 'ちゅん!'

注目ポイントは、どのコードもgreetという同名のメソッドを使用している点です。
にも関わらず、3つのコードで出力が違うのはなぜでしょうか。
それは、animalというレシーバのクラスがDogCatBirdと種類が違うためです。

このように、同一名称のメソッドであるにもかかわらず、クラスの違いによって振る舞いに差異が現れることを多態性(ポリモーフィズム)と言います。

多態性の何が嬉しいのか

多態性が何なのかはなんとなくわかっていただけたかと思います。
では、本題の「多態性を利用して何が嬉しいのか」について書いていきたいと思います。

先ほどの動物の例を忘れ、以下のような3パターンの出力をランダムに行うプログラムを考えてみます。

【今日のお天気は晴れです!】
お洋服情報: あついかも!半袖で出かけちゃう?
持ち物情報: 荷物は少ない方がいいね!タオルをわすれずに!
おでかけ情報: いい天気!歩いていこっ
【今日のお天気は雨です!】
お洋服情報: 長靴はいちゃえ!
持ち物情報: 傘持って行こうね!
おでかけ情報: 濡れるとやだな、電車にのろう!
【今日のお天気は雪です!】
お洋服情報: コートとマフラー忘れずに!
持ち物情報: さむいよ!カイロ持っていこうね!
おでかけ情報: 遅れちゃうかも!タクシー使おう!

お天気(晴れ・雨・雪)をランダムに選択し、その天気に応じたお洋服情報・持ち物情報・おでかけ情報を表示するプログラムです。
あなたならこのプログラムをどう実装しますか?

多態性を使用しない例

以下の作戦で実装してみようと思います。

  • お洋服クラス・持ち物クラス・おでかけクラスを作る
  • 各クラスの中で入力値による分岐を書く

それでは、いざ実装。

require './cloth'
require './baggage'
require './transport'

class Main
  def self.exec(weather)
    cloth = Cloth.new(weather)
    baggage = Baggage.new(weather)
    transport = Transport.new(weather)

    puts "【今日のお天気は#{weather}です!】"
    puts "お洋服情報: #{cloth.info}"
    puts "持ち物情報: #{baggage.info}"
    puts "おでかけ情報: #{transport.info}"
  end
end

# 実行
weather_list = ['晴れ', '雨', '雪']
weather_list.shuffle!
Main.exec(weather_list[0])
class Cloth

  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      'あついかも!半袖で出かけちゃう?'
    when '雨'
      '長靴はいちゃえ!'
    when '雪'
      'コートとマフラー忘れずに!'
    end
  end
end
class Baggage

  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      '荷物は少ない方がいいね!タオルをわすれずに!'
    when '雨'
      '傘持って行こうね!'
    when '雪'
      'さむいよ!カイロ持っていこうね!'
    end
  end
end
class Transport
  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      'いい天気!歩いていこっ'
    when '雨'
      '濡れるとやだな、電車にのろう!'
    when '雪'
      '遅れちゃうかも!タクシー使おう!'
    end
  end
end

無事に実装できました!やったね!

突然の仕様変更

?「実装完了したところ悪いんだけど、くもりのケースを考慮するのを忘れていたよ。」
?「くもりの時の出力は以下みたいな感じ!修正よろしく!」

【今日のお天気はくもりです!】
お洋服情報: 少し肌寒いかも!羽織ものを持っていこう!
持ち物情報: 折り畳み傘があるとあんしん!
おでかけ情報: 車で出かけるのがおすすめ!

やれやれ...
というわけで、くもりケースを追加していきたいと思います。

まず、Mainクラスからですが、Mainクラス自体を触る必要はありません。
ただし、くもりの条件を増やすため、実行部は以下のように変更する必要があります。

# weather_list = ['晴れ', '雨', '雪'] 修正前のコード
weather_list = ['晴れ', '雨', '雪', 'くもり']  # 修正後のコード

そして、Clothクラス、Baggageクラス、Transportクラスにそれぞれ以下のような処理を追加します。

class Cloth

  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      'あついかも!半袖で出かけちゃう?'
    when '雨'
      '長靴はいちゃえ!'
    when '雪'
      'コートとマフラー忘れずに!'
    when 'くもり'  # ここを追加
      '少し肌寒いかも!羽織ものを持っていこう!'  # ここを追加
    end
  end
end
class Baggage

  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      '荷物は少ない方がいいね!タオルをわすれずに!'
    when '雨'
      '傘持って行こうね!'
    when '雪'
      'さむいよ!カイロ持っていこうね!'
    when 'くもり'  # ここを追加
      '折り畳み傘があるとあんしん!'  # ここを追加
    end
  end
end
class Transport
  def initialize(weather)
    @weather = weather
  end

  def info
    case @weather
    when '晴れ'
      'いい天気!歩いていこっ'
    when '雨'
      '濡れるとやだな、電車にのろう!'
    when '雪'
      '遅れちゃうかも!タクシー使おう!'
    when 'くもり'  # ここを追加
      '車で出かけるのがおすすめ!'  # ここを追加
    end
  end
end

修正完了しました!

この修正のどこが嫌なのか

今回は簡単なサンプルコードなので大した修正量ではなかったですが、この修正には以下のような嫌ポイントが潜んでいました。

【修正の必要なクラスが多い】
上記の通り、今回はCloth, Baggage, Transportの3クラスを修正する必要がありました(実行部を除く)。実務で使用するコードではもっとたくさんのクラスがあるt想定できます。その大量のクラスたちの中からこの3クラスを見つけ出さなくてはなりません。さらに、たくさんのクラスに触れれば触れるほど、変更によるバグ発生の危険性が高まってしまいます。

【似たような分岐処理が3クラスに散らばっている】
上記の例では、3クラスにcase文が書かれています。どうみても同じ分岐処理なのに、違う箇所に何度も書かれているのは読みづらいですよね。さらに、この書き方には修正漏れのリスクがあります。今回使用したのは3クラスだけなので、修正漏れは発生しづらいですが、似たような分岐処理が数十クラスに渡っている場合、全てのクラスを修正して回るのは大変ですし、修正漏れのリスクを高めてしまいます。

多態性を利用する例

上記のような嫌ポイントを解決するため、作戦を変更してみたいと思います。

  • 晴れ・雨・雪の3クラスを作成する
  • 各クラスは自身の天気に対して返却するお洋服・持ち物・おでかけの情報を持つ

実際に書いてみましょう。

require './sunny'
require './rainy'
require './snowy'

class Main
  def self.exec(weather_input)
    weather = case weather_input
              when '晴れ'
                Sunny.new
              when '雨'
                Rainy.new
              when '雪'
                Snowy.new
              end

    puts "【今日のお天気は#{weather.value}です!】"
    puts "お洋服情報: #{weather.cloth}"
    puts "持ち物情報: #{weather.baggage}"
    puts "おでかけ情報: #{weather.transport}"
  end
end

# 実行
weather_list = ['晴れ', '雨', '雪']
weather_list.shuffle!
Main.exec(weather_list[0])
require './weather'

class Sunny < Weather
  attr_reader :value

  def initialize
    @value = '晴れ'
  end

  def cloth
    'あついかも!半袖で出かけちゃう?'
  end

  def baggage
    '荷物は少ない方がいいね!タオルをわすれずに!'
  end

  def transport
    'いい天気!歩いていこっ'
  end
end
require './weather'

class Rainy < Weather
  attr_reader :value

  def initialize
    @value = '雨'
  end

  def cloth
    '長靴はいちゃえ!'
  end

  def baggage
    '傘持って行こうね!'
  end

  def transport
    '濡れるとやだな、電車にのろう!'
  end
end
require './weather'

class Snowy < Weather
  attr_reader :value

  def initialize
    @value = '雪'
  end

  def cloth
    'コートとマフラー忘れずに!'
  end

  def baggage
    'さむいよ!カイロ持っていこうね!'
  end

  def transport
    '遅れちゃうかも!タクシー使おう!'
  end
end
class Weather
  attr_reader :value

  def initialize; end

  def cloth
    raise NotImplementedError
  end

  def baggage
    raise NotImplementedError
  end

  def transport
    raise NotImplementedError
  end
end

同様に仕様変更してみる

先ほどの例と同じように、くもりの条件を足してみたいとおもいます。

require './sunny'
require './rainy'
require './snowy'
require './cloudy'  # ここを追加

class Main
  def self.exec(weather_input)
    weather = case weather_input
              when '晴れ'
                Sunny.new
              when '雨'
                Rainy.new
              when '雪'
                Snowy.new
              when 'くもり'  # ここを追加
                Cloudy.new  # ここを追加
              end

    puts "【今日のお天気は#{weather.value}です!】"
    puts "お洋服情報: #{weather.cloth}"
    puts "持ち物情報: #{weather.baggage}"
    puts "おでかけ情報: #{weather.transport}"
  end

end

weather_list = ['晴れ', '雨', '雪', 'くもり']  # ここを追加
weather_list.shuffle!

Main.exec(weather_list[0])

まず、Mainクラスに分岐の条件が1つ増えました。

次にCloudyクラスを作成してみます。

require './weather'

class Cloudy < Weather
  attr_reader :value

  def initialize
    @value = 'くもり'
  end

  def cloth
    '少し肌寒いかも!羽織ものを持っていこう!'
  end

  def baggage
    '折り畳み傘があるとあんしん!'
  end

  def transport
    '車で出かけるのがおすすめ!'
  end
end

これで実装は完了です。

今回の変更では何が嬉しかったのか

さっきの変更と要件はまったく同じでしたが、今回の変更は何が良かったのでしょうか。

【既存クラスに修正をする必要がなかった】
今回の修正で注目すべきなのは、SunnyRainySnowyという既存クラスに触れないで実装を完了できた点です。前述した通り、既存クラスを触らないといけないということは、それだけバグを生み出してしまう可能性を高めてしまうということです。

【Cloudyクラスの実装が明確】
今回新たにCloudyクラスを実装しましたが、どのように実装すべきかは親クラスであるWeatherを見れば一目瞭然です。新しいクラスを作成する際にも迷わずに素早く実装を終えることができます。

なぜこのような恩恵を受けることができたのでしょうか。
それは、分岐処理をインスタンス生成箇所に集結させ、振る舞いの差異は多態性によって表現したためです。
このように、多態性を利用することで分岐処理を散らばらせるのを防ぎ、仕様変更に強いコードを書くことができます。

[おまけ]
今回の例では、Mainというクライアントコードにインスタンス生成のロジックが露呈してしまっています。本来ならば隠蔽したいところですが、多態性の話から外れてしまうのでまたの機会にしておきます。

まとめ

今回は初学者の方向けに多態性について解説してみました。
この記事がオブジェクト指向プログラミングに興味を持ってもらえるきっかけになれば幸いです。

14
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?