オブジェクト指向とは何か

オブジェクト指向とは何か プログラミング

あなたは「オブジェクト指向とは何か」と訊かれたら、答えられるでしょうか。

「オブジェクト指向はソフトウェアをクラスとして定義し……」

「動物がスーパークラスで魚類や鳥類がサブクラスで……」

「現実世界のあらゆるものは情報と振る舞いをもつオブジェクトで……」

このように説明をされても、正直よくわかりません。

そんなときは、歴史を振り返ってみましょう。そうすれば、オブジェクト指向が2つの課題を解決するために生まれた技術であり、現実世界を表現するためのものではないことがわかります。

この記事で、オブジェクト指向の神秘のベールを剥ぎ取ってみせましょう。

オブジェクト指向誕生の歴史的経緯

オブジェクト指向は、従来のプログラミング言語の問題点を解決するために生まれました。

では、どのような問題があったのでしょうか。歴史を振り返れば、その答えがわかります。

最初のプログラミング言語であるアセンブリ言語

そもそもコンピュータは、2進数で書かれた機械語しか解釈できません。しかし機械語は人間が理解するには難しく、一部の専門家にしかプログラムを書くことができませんでした。

その状況を改善すべく、機械語を人間にもわかりやすい記号に置き換えて表現する方法が登場しました。それがアセンブリ言語です。

アセンブリ言語の登場で、プログラムは格段にわかりやすくなりました。また、間違いが減り、後からの修正も非常に楽になったのです。

しかしアセンブリ言語によるプログラムでは、命令をほんの少し間違えただけでも暴走してしまいました。また、わかりやすくなったとはいえ、実行命令を1つ1つ指定するプログラミングは非常に煩わしいものでした。

高級言語

そこで、より人間に親しみやすい表現形式を用いた高級言語が発明されました。

高級言語は、英単語や記号などを組み合わせてコードを記述します。機械語(やそれに1対1に対応するアセンブリ言語)では複数行にわたる命令を、1行で表現できるのでプログラミングの生産性や品質は大きく向上しました。

しかしコンピュータの普及と発展が爆発的に進んだため、生産性向上に対するニーズは収まりませんでした。

1968年には、NATO(北大西洋条約機構)の国際会議で「20世紀末には世界の総人口がプログラマになっても、増大するソフトウェア需要に追いつかない」というソフトウェア危機が宣言されたほどです。

構造化言語

これまでのプログラミング言語の進化は「より簡単に、より親しみやすく」という方向で進んできました。しかし、それだけではソフトウェア危機に対応できません。

そこで新たに登場した構造化言語(ALGOL、Pascal、C言語など)では「保守性向上と再利用促進」つまり既存のプログラムをより長く使い続ける方向に進化の舵を切ったのです。

保守性向上のために導入されたのが「基本三構造」です。

  • 順次進行(命令は上から順に実行される)
  • 条件分岐(if文、case文など)
  • 繰り返し(while文、for文など)

これにより、プログラムのわかりやすさは格段に向上しました。

そして再利用促進のために、サブルーチン(オブジェクト指向プログラミング言語ではメソッドと呼ばれる)の独立性が高められました。

サブルーチンの独立性を高めるには、呼び出し側(メインルーチン)と共有する情報を少なくする必要があります。共有する情報とは、変数に格納されたデータのことです。

プログラムのどこからでも呼び出せる変数を、グローバル変数と呼びます。このグローバル変数をいかに減らすかが、独立性向上の鍵となります。

そのために考案されたのが、ローカル変数引数の値渡しです。

ローカル変数は、サブルーチンの中だけで使われる変数です。サブルーチンの外では使えないので、メインルーチンと共有することはありません。

しかし、メインルーチンから値を受け取りたい場合もあります。そのための仕組みが引数です。メインルーチンがサブルーチンを呼び出す際に引数を渡すと、その値のコピーをサブルーチンが受け取ります。

これにより、サブルーチンで引数の値を変更しても、メインルーチン側の変数に影響を与えることがなくなりました。

残された課題

構造化言語で実現した理論は、現在では常識となりました。しかし、解決できない課題が2つ残ったのです。それがグローバル変数問題貧弱な再利用です。

グローバル変数問題解決のために考案されたローカル変数には、サブルーチン呼び出しが終わると消えてしまうという性質があります。ですので、サブルーチン呼び出し後も保持する必要がある情報は、依然としてグローバル変数に格納せざるを得ませんでした。

グローバル変数は、プログラムのどこからでも呼び出せます。つまりグローバル変数を変更すると、影響範囲を確かめるためにプログラム全体を調べなければならないのです。プログラムの規模が大きくなればなるほど、この問題は深刻になります。

もう一つの課題が、貧弱な再利用です。構造化言語で再利用できるのは、サブルーチンでした。言い換えれば、サブルーチンしか再利用できないということです。生産性向上には、より大規模な再利用が必要になります。

この2つの課題を解決したのが、オブジェクト指向です。次節では、オブジェクト指向がいかにして課題を解決したかを見ていきましょう。

オブジェクト指向の三大要素

オブジェクト指向プログラミング言語には、従来のプログラミング言語にはない3つの優れた仕組みが備わっています。それが「クラス」「ポリモーフィズム」「継承」です。

この三大要素こそ、グローバル変数問題と貧弱な再利用を解決するためのものなのです。

なおサブルーチンのことをオブジェクト指向ではメソッドと呼ぶため、以降ではメソッドと表記します。

クラス

クラスの役割は大きく分けて3つあります。

  • メソッドと変数をまとめる
  • クラス内だけで使う変数やメソッドを隠す
  • 1つのクラスからインスタンスをたくさん作る

1つずつご説明します。

メソッドと変数をまとめる

Rubyのコードを例に見ていきましょう。

例1
class MessagesController < ApplicationController
  before_action :set_group

  def index
    @message  = Message.new
    @messages = @group.messages.includes(:user)
  end

  def create
    @message = @group.messages.new(message_params)
    if @message.save
      respond_to do |format|
        format.html do
          redirect_to group_messages_path(@group),
                      notice: 'メッセージが送信されました'
        end
        format.json
      end
    else
      @messages = @group.messages.includes(:user)
      flash.now[:alert] = 'メッセージを入力してください。'
      render :index
    end
  end

  private

  def message_params
    params.require(:message).permit(:content, :image).merge(user_id: current_user.id)
  end

  def set_group
    @group = Group.find(params[:group_id])
  end
end

例1では、MessagesControllerクラスに4つのメソッド(index、create、message_params、set_group)と5つの変数(@message、@messages、@message、@messages、@group)がまとめられています。

このようにまとめると、それまで重複して記述していた数十行〜数百行を、クラスを呼び出すためのコード一行で実行することができます。それまでサブルーチンしか再利用できなかったのが、サブルーチン(メソッド)をまとめたものを再利用できるようになったのです。

まとめることのもう一つの効果が、メソッドの名前です。構造化言語では、全てのサブルーチン に異なる名前をつける必要がありました。

しかしオブジェクト指向言語では、同一クラス内のメソッド名が重複しなければ問題ありません。つまりクラスが違えば、同じ名前のメソッドが存在しても良いのです。例えばindexメソッドが、MessagesControllerクラスとUersControllerクラスにあっても構いません。

適切な名前をつけてクラスにまとめることは、メソッドを探しやすくする効果もあります。これも再利用を促進するための重要な機能の一つです。

クラス内だけで使う変数やメソッドを隠す

MessagesControllerクラスにまとめられたメソッドと変数のうち、message_paramsメソッド、set_groupメソッド、@group変数は同じクラス内からのみ呼び出せるようにしたい、つまり他の場所から隠したいとします。

その場合、オブジェクト指向言語では「private」と記述します。こうすることでどこからでも呼び出せるグローバルな状態から、限定的にしか呼び出せないローカルな状態に変更することができます。

1つのクラスからインスタンスをたくさん作る

クラスはインスタンスをいくつでも作ることができます。

これにより同種の情報を複数同時に扱う処理であっても、クラスを1つ定義しておくだけで実現できます。

例えば、Foodクラスがあるとします。この中で@nameと@priceという変数を定義しておけば、ソバだろうとラーメンだろうと、インスタンスを作って名前と価格を引数として渡せば簡単に作ることができます。クラス内の処理は、インスタンスの数を意識する必要がありません。

これをクラスの仕組みがない従来の言語で実現するには、あらかじめ必要な数だけ変数領域を用意する必要があるため、それを処理するサブルーチンのロジックも複雑になってしまいます。

クラスさえ定義しておけば、インスタンスをいくつでも作ることができる。これも再利用促進のための強力な機能です。

また、インスタンス変数は他のクラスからアクセスできないようにできる点でローカル変数的でありながら、いったんインスタンスが作られれば必要なくなるまで存在し続けるグローバル変数的なところもあります。

グローバル変数は使いたくない。でも情報はサブルーチンの実行期間中を超えて保持したい。そんな要求に答えたのが、インスタンス変数なのです。

ポリモーフィズム

ポリモーフィズムとは、端的に言えば共通メインルーチンを作るための仕組みです。

誤解を恐れずに言うと、同じ名前のメソッドを複数のクラスで使えるようにする仕組みです。

例2
class Person
  def initialize(name)
    @name = name
  end

  def greet
    puts "はじめまして、#{@name}と申します。"
  end
end

class Friend < Person
  def greet
    puts "#{@name}です。はじめまして。"
  end
end

def greeting(who)
  who.greet
end

tanaka = Person.new("田中")
sato = Friend.new("佐藤")

greeting(tanaka) #=> はじめまして、田中と申します。
greeting(sato) #=> 佐藤です。はじめまして。

例2では同じ名前のメソッド(greeting)を呼び出しているのに、クラス(PersonかFriendか)によって異なる振る舞いをしています。このように、呼び出し側の記述を共通化できるのがポリモーフィズムです。

Ruby on Railsのようなフレームワークが可能になったのも、この仕組みのおかげです。

継承

継承とは、クラスの共通部分を切り出す仕組みです。

クラスが複数あれば、それぞれに共通するメソッドや変数がある場合もあります。その共通部分を1つのクラスにまとめて、他のクラスが参照できるようにする仕組みが継承というわけです。

例1に「class MessagesController < ApplicationController」という記述があります。これは、MessagesControllerクラスがApplicationControllerクラスを継承することの宣言です。

継承するクラス(例1の場合はMessagesControllerクラス)をサブクラス(または子クラス)、継承されるクラス(例1の場合はApplicationControllerクラス)をスーパークラス(または親クラス)と呼びます。

これも重複するコードをまとめて再利用を促すための仕組みです。

まとめ

以上見てきたように、オブジェクト指向はグローバル変数問題解決と再利用のさらなる促進のために生まれました。そのための仕組みが「クラス」「ポリモーフィズム」「継承」です。

決して、現実世界をそのままプログラムで実現するための技術ではありません。

そもそもコンピュータの得意技は「決まり切った仕事」「覚える仕事」です。であるならば、プログラムもその2つをより効率的にこなすために存在しているはず。もちろんオブジェクト指向もそのための技術です。必要以上にありがたがるものでも、恐れるものでもありません。

この記事が少しでも参考になったなら幸いです。あなたの毎日がハッピーでありますように。

参考書籍

コメント

タイトルとURLをコピーしました