N.Y.Cityのまちかど
introduction of programming language & OOP
プログラミング言語&オブジェクト指向 入門
プログラミング言語とは
「プログラム」とは
創成期のコンピュータは、新たな計算処理をコンピュータに行わせようとする際、いちいちパッチボード(配線を差し替えるパネル)のケーブルをつなぎかえて回路を変更しないといけませんでした。
創成期のコンピュータ(ENIAC)*1 |
---|
その後、フォン・ノイマンらによって画期的な手法が提案されました。まずコンピュータの回路はそれまでのようにいちいち専用の回路を用意するのではなく、汎用的なものをあらかじめ用意しておきます。そして、紙(パンチカードやパンチテープ)やメモリなどにコンピュータに対する命令を記録しておき、それを逐次参照しながら汎用回路を順次動かして所望の機能を実現するという方式が提案されました。「プログラム内蔵方式」と呼ばれるこの方法は、コンピュータ本体はそのままに、プログラムを入れ替えるだけで様々な働きをしてくれるため、現在まで広く普及しています。
パンチカード(筆者個人が所蔵しているもの) 工学院大学でかつて実際に使用されていたパンチカード。 ただしこれはFORTRANという高級言語を記述するための パンチカードで、ENIAC当時のパンチカードとは意味が異なる |
---|
パンチテープ*2 中央付近の小さな穴の列は紙送りと位置検出のためのもの。 まばらに開けられた大きめの穴がデータやプログラムを表す。 |
---|
コンピュータプログラムとは「コンピュータに対する動作指示の集合体」なのです。
「プログラミング言語」とは
「コンピュータに対して動作を指示するための言語」がプログラミング言語です。
世の中にあるプログラミング言語は、いったい何種類あるのでしょうか。筆者が思いつく限り列挙してみると、例えば次のようになります。
機械語、アセンブリ言語、 C言語、 C++、C# 、Visual C++ 、Visual Basic、Visual Basic for Application 、Ruby 、Perl、Python 、FORTRAN、COBOL 、ADA 、HSP、HyperTalk 、Max 、Java Script 、Objective-C、Apple Script 、Action Script、LOGO 、なでしこ、…
世の中には沢山のプログラミング言語があり、それぞれに特徴を持っています。
プログラミング言語の分類
さて、ちょっと思い出すだけで何十個も出てくるプログラミング言語ですが、これらを分類しようとする時、重要になる分類方法がいくつかあります。
- 低級言語か高級言語か
- インタープリタ/コンパイラ/バイトコード(方式)
- 手続き型言語か、オブジェクト指向言語か、…
このうち、3.については後でじっくり取り上げるとして、残る2つについて概説します。
低級言語か高級言語か
この分類は簡単です。世の中に数多くあるプログラミング言語のうち、低級言語に分類されるものは「機械語(マシン語)」と「アセンブリ言語」と呼ばれるものの2つしかありません。また、同じ「機械語」「アセンブリ言語」といっても、CPUごとに命令体系・文法が異なるというのも一つの特徴です。
文字の雰囲気から勘違いされやすいのですが「低級言語」が「高級言語」に劣っているわけではありません。
低級言語
- <機械語(マシン語)>
コンピュータのCPUが理解できる唯一の言語です。CPUが行うべき処理を2進数で書き表したものです。この2進数が直接、電圧の高い/低いと言う電気信号に置き換えられてCPUに渡されます。
2進数で表されるため、人間には非常に分かり辛い、扱い辛いものです。
00000000 00000000 00111110 00000010 00110010 00000000 00000001 00111110 00000001 00110010 00000001 00000001 00111010 00000001 00000001 01000111 00111010 00000000 00000001 10010000 10010000 11000010 00001101 00000001 00111110 00000000 00110010 00000000 00000001 01110110 |
2進数で表記された機械語(Zilog社のZ80というCPUのもの) |
---|
2進数ではあまりにも扱いづらいため、人間が読み書きする際には16進数表記にすることが良く行われます。以下の例は、先ほど2進数で書いたプログラムと同一のものを、16進で表記したものです。
00 00 3E 02 32 00 01 3E 01 32 01 01 3A 01 01 47 3A 00 01 90 90 C2 0D 01 3E 00 32 00 01 76 |
16進数で表記された機械語 (Zilog社のZ80というCPUのもの) |
---|
- <アセンブリ言語>
機械語はあまりにも人間が扱いにくいため、機械語が持つ各種命令に、アルファベットで記号(ニーモニック)をつけ、人間にも扱いやすくした言語が「アセンブリ言語」です。以下の例は先ほど2進数で書いたプログラムと同一のものを、アセンブリ言語で表記したものです。
X: DS 1
Y: DS 1 LD A,2 LD (X),A LD A,1 LD (Y),A LD A,(Y) LD B,A LD A,(X) SUB B SUB B JP NZ,(HLT) LD A,0 LD (X),A HLT: HALT |
アセンブリ言語(Zilog社のZ80というCPUのもの) |
---|
CPUは機械語しか理解できないため、アセンブリ言語で記述したプログラムを機械語に変換する必要があります。この「アセンブリ言語から機械語への翻訳作業」を「アセンブル」と言います。アセンブルを自動的に行う*3ソフトウェアを「アセンブラ」と言います。
「アセンブリ言語」ではなく「アセンブラ」や「アセンブラ言語」と表記している文献も多い(メーカーによってはこちらを正式名称としている)ですが、個人的には「assemble(=整理された)+ -y(性質を持つ)」言語という意味で「assembly(アセンブリ)」言語と呼ぶのが適切だと思います。
インタープリタ/コンパイラ/バイトコード(方式)
高級言語は、何らかの方式で機械語に変換されなければ実行できません。高級言語から機械語へ変換する方式は、主に3種類あります。
インタープリタ方式
インタープリタ(interpreter(通訳者)方式は、ソースコードを逐一機械語に通訳(インタープリット:interplet)しながら実行する方式です。BASICやHSPなどが採用しています。
気軽に動作させることができ、また実行時エラーが生じた際、ソースコードのどの部分でエラーが生じたのかが比較的容易に検証でき、デバッグが容易ですが、文法ミスや変数型不整合などのちょっとしたミスも実行してみないとわかりません。しかしその反面、いちいち通訳が必要なため、処理速度は低速になります。
ソースコードは共通で利用できますが、実行時に必要なインタプリタはアーキテクチャ(コンピュータの基本構造)別に用意する必要があります。
インタープリタ方式 |
---|
コンパイラ方式
コンパイラ(compiler(編集者))方式は、ソースコードを一括して機械語に翻訳(compile:編集という意味)しておき、翻訳語の機械語を直接実行する方式です。C言語などが採用しています。
あらかじめ全てを一括して機械語に翻訳するため。処理速度が高速であることが特徴です。コンパイル時に文法チェックがなされるため、文法ミスや変数型不整合などのミスをコンパイル時に検出できますが、デバッグはやや難しくなります。
ソースコードは共通で理容できますが、コンパイラはアーキテクチャ別に用意する必要があり、コンパイラが生成する機械語は各アーキテクチャ専用のものとなります。
コンパイラ方式 |
---|
バイトコード方式
ソースコードをあらかじめバイトコード(中間言語)に翻訳しておく方式です。このバイトコードはいわば「擬似機械語」の役割を果たしており、機械語と同じような構造を有していますが、どのアーキテクチャでも直接実行できません。実行時には、各アーキテクチャごとに用意された実行環境が、バイトコードを機械語に翻訳しながら実行します。JavaやRubyなどが採用しています。
バイトコード方式の特徴は移植性の高さです。バイトコードはアーキテクチャに依存しないため、共通のバイトコードでいかなるアーキテクチャ上で実行できます。この点においては、共通のソースコードから実行できるインタプリタ方式と変わりませんが、より機械語に近い構造のバイトコードにあらかじめ翻訳されているため、バイトコード方式の方がインタプリタ方式より処理速度は高速になります。
バイトコード方式 |
---|
手続き型言語とオブジェクト指向型言語
高級言語の分類として、無視することのできない非常に重要なものは「手続き型言語かオブジェクト指向型言語であるか」です。(これ以外の分類も存在するが、ここではこのツートップに絞って話をします。
手続き型言語
手続き型言語は、「コンピュータが行う処理を(原則として)時系列順に沿って記述する」コンピュータ言語です。。
非常に古典的な言語方式であり、C言語などが採用しています。また低級言語(機械語、アセンブリ言語)も手続き型に属する言語です。。
イメージとしては、誰か人に仕事を頼む時に渡す「おつかいメモ」を思い浮かべればいいでしょう。相手にやって欲しい仕事を、順番にメモに書いて渡し、それを先頭から順番にこなしていってもらうわけです。
仕事がある妻が夫に家事を「おつかいメモ」を介して頼む |
---|
処理を頼みたいプログラマが、コンピュータに依頼する処理を「プログラム」を介して頼む |
---|
手続き型言語はコンピュータにやって欲しいことを、コンピュータにやって欲しい順番に書いていくため、コンピュータにとっては非常に都合の良い表現です。逆にプログラムを作る人間は、コンピュータの立場に立って処理を考え、プログラムを書かなくてはならず、プログラマの負担になります。(洗濯をしておいてと頼んでも、夫が洗剤がどこに置いてあるか知らない場合、指示を実行することができません。おつかいメモを書く時も、頼む相手がわかるレベルにまで指示を掘り下げて記述する必要があります。)
さらなる問題として、大規模プログラムに対する限界があります。OSのように大規模なプログラムを手続き型言語で書こうとすると、プログラムが複雑になり過ぎてしまいます。そのため、製作効率の低下、バグの増加(プログラム品質の低下)、事後メンテナンスが困難である、などのデメリットを抱えています。
これを解決するために、プログラムを構成する処理を「サブルーチン(または手続き)」と呼ばれる固まりに分割し、また混乱の元凶となりやすいジャンプ命令を原則として廃止する代わりに
-
順接
- 命令を順番に実行する
- 分岐
- ある条件に従って、プログラムの流れを2つ(またはそれ以上)に分岐させる
- 反復
- ある条件に従って、プログラムの指定された部分を反復実行する
という3つの要素でプログラムの流れを整理しようとする流れが産まれました。これは「構造化プログラミング」と呼ばれ、広く普及しました。しかし、大規模プログラムを構築する時の問題は完全に解決されたわけではなく、さらなる工夫が求められました。
例えば、じゃんけんをシミュレートプログラムを考えてみます。実行するたびにランダムに手の形を2つ決め、勝負を判定して表示します。
このプログラムはRubyで書かれています。Rubyはオブジェクト指向型言語ですが、あえてここでは手続き型言語(風)に書いています。
01:#####################################
02:# RPS_procedual.rb # 03:##################################### 04: 05:##################################### 06:# 定数の定義 # 07:##################################### 08:#数字の0,1,2がグー,パー,チョキに対応 09:GU = 0 10:PA = 1 11:CH = 2 12: 13: 14:##################################### 15:# ここからメイン処理 # 16:##################################### 17: 18:#aさんの出す手を決める 19:a_hand = rand(3) #0~2の整数をランダムに代入 20: 21:#bさんの出す手を決める 22:b_hand = rand(3) #0~2の整数をランダムに代入 23: 24:#aさんとbさんの手を表示 25:print "a_hand=",a_hand,"\t b_hand=",b_hand,"\n" 26: 27: 28:#勝負を判定する 29:print "Result:" 30:if a_hand == b_hand then 31: print "It's draw." 32:end 33: 34:if (a_hand==GU && b_hand==CH) || (a_hand==PA && b_hand==GU) || (a_hand==CH && b_hand==PA) then 35: print "a win!" 36:end 37: 38:if (b_hand==GU && a_hand==CH) || (b_hand==PA && a_hand==GU) || (b_hand==CH && a_hand==PA) then 39: print "b win!" 40:end 41: |
じゃんけんプログラム(手続き型版)ソースプログラム |
---|
上記プログラムを実行した例を示します。
[カレントディレクトリ]>ruby RPS_procedual.rb
a_hand=0 b_hand=2 Result:a win! [カレントディレクトリ]>ruby RPS_procedual.rb a_hand=2 b_hand=2 Result:It's draw. [カレントディレクトリ]>ruby RPS_procedual.rb a_hand=0 b_hand=1 Result:b win! [カレントディレクトリ]> |
じゃんけんプログラム(手続き型版)実行結果例 |
---|
ソースコードの動作を見てみましょう。
- 09~11行目では、プログラムの見た目を分かりやすくするため、定数を定義しています。
- 19行目では、コンピュータのメモリ上の変数a_handに乱数で0,1,2のどれかを入れます.aさんが出そうとしている手に相当します.
- 22行目では、コンピュータのメモリ上の変数b_handに乱数で0,1,2のどれかを入れます.bさんが出そうとしている手に相当します.
- 25行目で、(コンピュータのメモリ上にある)a_hand, b_handを画面に表示しています。
- 28行目から、勝負判定処理が始まります。(コンピュータのメモリ上にある)a_hand, b_handを比較します.
-
30行目の分岐条件は「両方の手が同じ時」(すなわちあいこの時)
- 画面にはあいこ(draw)と(コンピュータの画面に)表示されます。
- 34行目の分岐条件「aがグーでbがチョキまたはaがパーでbがグーまたはaがチョキでbがパー」(すなわちaさんが勝ちの時)
- 画面にはaの勝ち(a win)と(コンピュータの画面に)表示されます。
- 38行目の分岐条件「bがグーでaがチョキまたはbがパーでaがグーまたはbがチョキでaがパー」(すなわちbさんが勝ちの時)
- 画面にはbの勝ち(b win)と(コンピュータの画面に)表記されます。
-
30行目の分岐条件は「両方の手が同じ時」(すなわちあいこの時)
この説明,特に下線部を見ると,命令の宛先はすべて(コンピュータ)に向いていることが分かります.
オブジェクト指向型言語
手続き型言語、構造化プログラミングに続く新たな潮流として、「オブジェクト指向型言語」が産まれました。最初の本格的なオブジェクト指向型言語はSmalltalkと言われています。
オブジェクト指向の基本的な発想は「データ」と「そのデータを扱うために必要な処理」を1つの固まりにした「オブジェクト」というものを生成し、それらが情報をやり取りする中で処理が進んでいくという考え方に基づいて動作します。
先ほど、手続き型言語の説明をしたときに例として示したじゃんけんプログラムを,オブジェクト指向らしく書いたものを紹介します.
1:#####################################
02:# RPS_objective.rb # 03:##################################### 04: 05:##################################### 06:# オブジェクトの定義 # 07:##################################### 08: 09:#じゃんけんする人のクラス 10:class Player 11: @myname #名前 12: @myhand #手の形 13: 14: #初期化(名前の決定) 15: def initialize(name) 16: @myname = name 17: end 18: 19: #自分の名前を伝える 20: def tell_name 21: return @myname 22: end 23: 24: #自分の手を伝える 25: def tell_hand 26: return @myhand 27: end 28: 29: #自分の手を選ぶ 30: def choice 31: @myhand = rand(3) 32: end 33: 34:end 35: 36:#じゃんけんの審判クラス 37:class Referee 38: 39: #数字の0がグー、数字の1がパー、数字の2がチョキとする 40: GU = 0 41: PA = 1 42: CH = 2 43: 44: #二人の名前と手の形を見せてもらって、勝ち負けを判定する 45: def judgement(a_name,a_hand,b_name,b_hand) 46: if a_hand == b_hand then 47: print "It's draw." 48: end 49: 50: if (a_hand==GU && b_hand==CH) || (a_hand==PA && b_hand==GU) || (a_hand==CH && b_hand==PA) then 51: print a_name," win!" 52: end 53: 54: if (b_hand==GU && a_hand==CH) || (b_hand==PA && a_hand==GU) || (b_hand==CH && a_hand==PA) then 55: print b_name," win!" 56: end 57: 58: end 59: 60:end 61: 62: 63:##################################### 64:# ここからメイン処理 # 65:##################################### 66: 67:a_pl = Player.new("a_hand") 68:b_pl = Player.new("b_hand") 69: 70:ref = Referee.new #refは審判です 71: 72: 73:#aさんの出す手を決める 74:a_pl.choice #a_plに手を決めてもらう 75: 76: 77:#bさんの出す手を決める 78:b_pl.choice #b_plに手を決めてもらう 79: 80:#aさんとbさんの手を表示 81:print a_pl.tell_name,"_hand=",a_pl.tell_hand,"\t ",b_pl.tell_name, "_hand=",b_pl.tell_hand,"\n" 82: 83:#審判クラスに、aとbと名前、手の形を見せて判定させる 84:ref.judgement(a_pl.tell_name,a_pl.tell_hand,b_pl.tell_name,b_pl.tell_hand) |
じゃんけんプログラム(オブジェクト指向版)ソースプログラム |
---|
このプログラムの解説を,先ほどと同じようにしてみると,次のようになります.
-
9行目からオブジェクト(正しくはクラス:詳しくは後述)の定義をする
-
10行目 じゃんけんする人のクラスPlayerを作る
- 11,12行目 Playerはmyname,myhandという2つのパラメータ(属性)を持つ
- 15-17行目 Playerは生成される時,名前(name)を受け取ってmynameに記録する
- 20-22行目 Playerはtell_nameというメソッド(機能)を持ち,これを呼ばれるとmyname(自分の名前)を返す.
- 24-27行目 Playerはtell_handというメソッド(機能)を持ち,これを呼ばれるとmyhand(自分の手)を返す.
- 30-32行目 Playerはchoiceというメソッド(機能)を持ち,これを呼ばれると乱数で0,1,2のどれかを選び,myhand(自分の手)に記録する.
- 37行目 じゃんけんの審判を行うクラスRefereeの定義をする
- 40-42行目 定数の定義
- 45-58行目 Refereeはjudgementというメソッド(機能)を持ち,a_name,a_hand(プレイヤーaの名前と手)b_name,b_hand(プレイヤーbの名前と手)と共にこれを呼ぶと,じゃんけんの結果を判定して表示する.
-
10行目 じゃんけんする人のクラスPlayerを作る
- 67行目から,メインの処理が記述される.
- 67-68行目 Playerクラスのインスタンス(実態)として a_pl,b_plを生成する(プレイヤーの準備)
- 70行目 Refereeクラスのインスタンスとしてrefを生成する(審判の準備)
- 74行目 (a_pl)のchoiceメソッドを呼んで,手を決めてもらう
- 78行目 (b_pl)のchoiceメソッドを呼んで,手を決めてもらう
- 81行目 画面に(a_pl,b_plの)tell_name,tell_handメソッドを使ってa_pl,b_plの名前と手を表示する
- 84行目 refのjudgementメソッドに(a_pl,b_plの)tell_name,tell_handメソッドを使ってa_pl,b_plの名前と手を渡し,じゃんけんの結果を判断して表示してもらう
この説明,特に下線部を見ると,命令の宛先はほとんどオブジェクトに向いています。メソッド(動作)とパラメータ(属性)を備えたクラスを作り、そのインスタンス(実態)を用意したら、あとはインスタンス同士が互いの機能を呼び合って(メッセージパッシング)処理を進めていく。これがオブジェクト指向の中心となる考え方です。
ここで示した例では、Player、Refereeという2つのクラスを自分で定義していますが、良く使うクラスについてはプログラミング環境の方であらかじめ定義されているので、わざわざ自分で作る必要はありません。
オブジェクト指向を使うメリット(特徴的な機能)
カプセル化
プログラムの機能を、データとそのデータを扱う処理をかたまりにした「クラス」単位で扱うことで、細々した内部の処理がどう行われているのかを隠し、守る働きがあり、これを「カプセル化」による「情報隠蔽」と言います。
クラスが一度定義されれば、それを使う人はクラスの内部でどんな処理が行われているのかを考えることなく、ただクラスの持っている機能(メソッド)の呼び出し方だけを知れば、そのクラスを使えます。
オブジェクトのプロパティは、原則としてそのオブジェクトの内部からしか参照したり、書きこんだりできません。外部からプロパティを読み書きしたい場合は、そのような機能を持つメソッド(アクセサ)を定義し、そこからアクセスしないといけません。この特性を逆にとらえれば、外部からいじってほしくないプロパティは、アクセサを用意しないことで外部から勝手に参照されたり、書き換えられることを防ぐ事ができます。
(クラスの)継承
先の例にあった「クラス」とは、オブジェクトの設計図に相当するものです。クラスはそれだけではあまり意味を持ちません。クラスからインスタンス(実体)を作ってそこにしかるべきパラメータを与え、動作させる事でプログラムが動きます。クラスのメリットは1つのクラスから複数のインスタンスが作れるため、同じ機能を持ったオブジェクトを容易に多数作って動かすことができることです。
同じ機能ではなく似ているけど違う機能が欲しい、ということは、プログラミングをしているとしばしばあることです。旧来の(オブジェクト指向でない)プログラムでは、関数(サブルーチン)のソースをコピー&ペーストして名前を変えて改造し…というやり方に頼らざるを得ませんでしたが、オブジェクト指向の場合は「継承」(インヘリタンス)という機能が使えます。
継承は新しく作ったクラス(仮にCと呼びます)と、既存のクラス(仮にPと呼びます)の間に「親―子」の関係を作ります。この場合Pが親、Cが子となり、「CはPを継承している」と言います。
Cは、Cのクラス宣言で定義されたメソッドやプロパティだけでなく親(P)機能(メソッド・プロパティ)を引き継ぎます。じゃんけんプログラムで例を示しましょう、次のプログラムは、Refereeクラスを継承したReferee_Newというクラスを作っています。
1:#####################################
2:# RPS_inheritance.rb # 3:##################################### 4: 5:##################################### 6:# オブジェクトの定義 # 7:##################################### 8: 9:#じゃんけんする人のクラス 10:class Player 11: @myname #名前 12: @myhand #手の形 13: 14: #初期化(名前の決定) 15: def initialize(name) 16: @myname = name 17: end 18: 19: #自分の名前を伝える 20: def tell_name 21: return @myname 22: end 23: 24: #自分の手を伝える 25: def tell_hand 26: return @myhand 27: end 28: 29: #自分の手を選ぶ 30: def choice 31: @myhand = rand(3) 32: end 33: 34:end 35: 36:#じゃんけんの審判クラス 37:class Referee 38: 39: #数字の0がグー、数字の1がパー、数字の2がチョキとする 40: GU = 0 41: PA = 1 42: CH = 2 43: 44: #二人の名前と手の形を見せてもらって、勝ち負けを判定する 45: def judgement(a_name,a_hand,b_name,b_hand) 46: if a_hand == b_hand then 47: print "It's draw." 48: end 49: 50: if (a_hand==GU && b_hand==CH) || (a_hand==PA && b_hand==GU) || (a_hand==CH && b_hand==PA) then 51: print a_name," win!" 52: end 53: 54: if (b_hand==GU && a_hand==CH) || (b_hand==PA && a_hand==GU) || (b_hand==CH && a_hand==PA) then 55: print b_name," win!" 56: end 57: 58: end 59: 60:end 61: 62:#Refereeクラスを継承した新たな審判クラス 63:class Referee_New < Referee 64: @myname #名前 65: 66: #初期化(名前の決定) 67: def initialize(name) 68: @myname = name 69: end 70: 71: #自分の名前を伝える 72: def tell_name 73: return @myname 74: end 75: 76: 77:end 78: 79: 80:##################################### 81:# ここからメイン処理 # 82:##################################### 83: 84:a_pl = Player.new("a_hand") 85:b_pl = Player.new("b_hand") 86: 87:ref = Referee_New.new("Mr.Referee") #refは(継承によって新しく作った)審判です 88: 89: 90:#aさんの出す手を決める 91:a_pl.choice #a_plに手を決めてもらう 92: 93: 94:#bさんの出す手を決める 95:b_pl.choice #b_plに手を決めてもらう 96: 97:#aさんとbさんの手を表示 98:print a_pl.tell_name,"_hand=",a_pl.tell_hand,"\t ",b_pl.tell_name, "_hand=",b_pl.tell_hand,"\n" 99: 100:#判定に先立って審判に名乗ってもらう 101:print "I am ",ref.tell_name,"!!\n" 102: 103:#審判クラスに、aとbと名前、手の形を見せて判定させる 104:ref.judgement(a_pl.tell_name,a_pl.tell_hand,b_pl.tell_name,b_pl.tell_hand) |
じゃんけんプログラム(審判も名乗る版)ソースプログラム |
---|
上記プログラムを実行した例を以下に示します。
[カレントディレクトリ]>ruby RPS_inheritance.rb
a_hand=0 b_hand=2 I am Mr.Referee Result:a win! [カレントディレクトリ]>ruby RPS_inheritance.rb a_hand=2 b_hand=2 I am Mr.Referee Result:It's draw. [カレントディレクトリ]>ruby RPS_inheritance.rb a_hand=0 b_hand=1 I am Mr.Referee Result:b win! [カレントディレクトリ]> |
じゃんけんプログラム(審判も名乗る版)実行結果例 |
---|
Referee_Newは、旧来のRefereeに「名前を名乗る」機能(具体的には@mynameというプロパティとtell_nameというメソッド)を追加したものです。63行目の「< Referee 」が、「Referee_NewはRefereeを継承する」という宣言となります。(Referee_Newの定義は63~74行目)
87行目で、Referee_Newのインスタンスrefが作られます。そしてrefは101行目でtell_nameメソッドを呼び出され、自分の名前を名乗っています。
注目してほしいのは、Referee_Newにはtell_nameメソッドしか定義されていないのに、104行目でjudgementメソッドを呼び出され、それが動作している所です。Referee_Newクラスは、自分の定義にないメソッド(この例ではjudgement)が呼び出されると、その呼び出しを親(この場合Referee)に投げます。judgementメソッドはReferee_Newメソッドには定義されていませんが、その親であるRefereeに定義されているので、正常に動作するのです。
(メソッドの)オーバーライド
クラスの継承では、既存のクラスに新たな機能(メソッド・プロパティ)を「付け足す」ことができました。
新たな機能を加えるのではなく、「既存の機能を書き変えたい」時には、メソッドのオーバーライドを行います。オーバーライドを行う事で。子クラスは親クラスのメソッドを「上書き」することができます。
それでは、オーバーロードの例を「ズルじゃんけん」プログラムで示しましょう。ズルじゃんけんとは、通常のグー・チョキ・パーの他、小指と薬指だけを閉じた手の形を「グーチョキパー」と称し、相手がどんな手を出そうとも勝てる(互いにグーチョキパーならあいこ)というルール(?)のじゃんけんです。
プログラムでは、Playerクラスを継承したSly_Player(ずるいプレイヤーという意味)クラスを作っています。Sly_Playerクラスの定義では、Playerに存在するchoiceメソッドを書き変え、手の形を表す値を0~3の四種類の中から選んでいます(3がグーチョキパーに相当)
また、それに合わせ、Refereeクラスの定義も多少変更しています(これはオーバーライドではなく、単純に書き変えています)
1:#####################################
2:# RPS_sly.rb # 3:##################################### 4: 5:##################################### 6:# オブジェクトの定義 # 7:##################################### 8: 9:#じゃんけんする人のクラス 10:class Player 11: @myname #名前 12: @myhand #手の形 13: 14: #初期化(名前の決定) 15: def initialize(name) 16: @myname = name 17: end 18: 19: #自分の名前を伝える 20: def tell_name 21: return @myname 22: end 23: 24: #自分の手を伝える 25: def tell_hand 26: return @myhand 27: end 28: 29: #自分の手を選ぶ 30: def choice 31: @myhand = rand(3) 32: end 33: 34:end 35: 36:#ズルじゃんけんをする人のクラス 37:class Sly_Player < Player 38: 39: #choiceメソッドを上書きする 40: def choice 41: @myhand = rand(4) #グーチョキパーを含む4種類の手の中から選ぶようにする 42: end 43: 44: 45:end 46: 47:#じゃんけんの審判クラス 48:class Referee 49: 50: #数字の0がグー、数字の1がパー、数字の2がチョキとする 51: GU = 0 52: PA = 1 53: CH = 2 54: SLY = 3 55: 56: #二人の名前と手の形を見せてもらって、勝ち負けを判定する 57: def judgement(a_name,a_hand,b_name,b_hand) 58: if a_hand == b_hand then 59: print "It's draw." 60: end 61: 62: if (a_hand==GU && b_hand==CH) || (a_hand==PA && b_hand==GU) || (a_hand==CH && b_hand==PA) then 63: print a_name," win!" 64: end 65: 66: if (b_hand==GU && a_hand==CH) || (b_hand==PA && a_hand==GU) || (b_hand==CH && a_hand==PA) then 67: print b_name," win!" 68: end 69: 70: if (a_hand == SLY) then 71: print a_name," win!" 72: end 73: 74: if (b_hand == SLY) then 75: print b_name," win!" 76: end 77: 78: end 79: 80:end 81: 82:#Refereeクラスを継承した新たな審判クラス 83:class Referee_New < Referee 84: @myname #名前 85: 86: #初期化(名前の決定) 87: def initialize(name) 88: @myname = name 89: end 90: 91: #自分の名前を伝える 92: def tell_name 93: return @myname 94: end 95: 96: 97:end 98: 99: 100:##################################### 101:# ここからメイン処理 # 102:##################################### 103: 104:a_pl = Sly_Player.new("a_hand") 105:b_pl = Player.new("b_hand") 106: 107:ref = Referee_New.new("Mr.Referee") #refは(継承によって新しく作った)審判です 108: 109: 110:#aさんの出す手を決める 111:a_pl.choice #a_plに手を決めてもらう 112: 113: 114:#bさんの出す手を決める 115:b_pl.choice #b_plに手を決めてもらう 116: 117:#aさんとbさんの手を表示 118:print a_pl.tell_name,"_hand=",a_pl.tell_hand,"\t ",b_pl.tell_name, "_hand=",b_pl.tell_hand,"\n" 119: 120:#判定に先立って審判に名乗ってもらう 121:print "I am ",ref.tell_name,"!!\n" 122: 123:#審判クラスに、aとbと名前、手の形を見せて判定させる 124:ref.judgement(a_pl.tell_name,a_pl.tell_hand,b_pl.tell_name,b_pl.tell_hand) |
じゃんけんプログラム(ズルじゃんけん版)ソースプログラム |
---|
上記プログラムを実行した例を以下に示します。
[カレントディレクトリ]>ruby RPS_sly.rb
a_hand_hand=3 b_hand_hand=0 I am Mr.Referee!! a_hand win! [カレントディレクトリ]> |
じゃんけんプログラム(ズルじゃんけん版)実行結果例 |
---|
この例では、a_plだけがSly_Playerなので、a_plの勝つ確率が高くなっているズルじゃんけんとなります。
(メソッドの)オーバーロード
多くの言語では、データの種類を表す「型」という概念があります。(例えばC言語ではint,float,,double,long,char など)そのような言語では、メソッド(または関数・サブルーチンなど)で「どんな型の値を扱うのか」があらかじめ定められています。定められた型でない型の値を渡しても、エラーとなってしまいます。
例えば、与えられた数値を2倍して返すという簡単な関数をC言語で書くと
int doubleup(int input){
return (input * 2.0); } となります。この関数はint型(整数型)の値を受け取り、それを2倍したint型の値を返す関数です。ですので、 |
printf("%d * 2 = %d",4,doubleup(4));
⇒ 4 * 2 = 8 |
のように使用出来ますが、この関数にfloat型(実数型)の値を渡すと、型が合わないため、エラーとなって使えません。
printf("%d * 2 = %d",3.14,doubleup(3.14));
⇒ 【エラーとなる】 |
この場合、仕方がないので、int用とfloat用で別々の関数を用意する事になります。
int i_doubleup(int input){
return (input * 2.0); } float f_doubleup(float input){ return (input * 2.0); } printf("%d * 2 = %d",4,i_doubleup(4)); ⇒ 4 * 2 = 8 printf("%d * 2 = %d",3.14,f_doubleup(3.14)); ⇒ ''3.14 * 2 = 6.28'' このため、場合によっては中身のほとんど変わらない関数をいくつも作らざるを得ない事があり、非効率的です。また、型によって呼び出す関数をいちいち切り替えるのも面倒です。 |
そこで、「オーバーロード」という概念が作られました。オーバーロードでは「名前が同じ」かつ「引数の型が違う」メソッドを1つのクラスの中に複数宣言できます。(通常、名前が同じメソッドは同じクラスの中には宣言できない)メソッドが呼ばれた時には、どんな型の値が引数として渡されているのかが自動的に確認され、しかるべき宣言のメソッドが実行されます。(型だけでなく、引数の数の違いでオーバーロードすることも可能ですが、本稿では割愛します)
C言語をオブジェクト指向化したC++では、次のようなプログラムを書くとオーバーロードが実現されます。
int doubleup(int input){
return (input * 2.0); } float doubleup(float input){ return (input * 2.0); } printf("%d * 2 = %d",4,doubleup(4)); ⇒ 4 * 2 = 8 printf("%d * 2 = %d",3.14,doubleup(3.14)); ⇒ ''3.14 * 2 = 6.28'' 「doubleupという名前」かつ「型が違う(intとfloat)」関数が2つ宣言されています。このdoubleup関数は、呼び出される度に引数inputの型を確認し、int型ならint型、float型ならfloat型用に定義された関数を実行します。 |
ちなみに、Ruby言語ではそもそもこの型という概念がない(厳密には全ての値はObjectという一つのクラスとしてみなされる)ので、オーバーロードという概念もありません。しかし、オーバーロード「風」にふるまうプログラムは書けます。
def doubleup(input)
Case input when Integer ~Integerの時の処理~ when Float ~Floatの時の処理~ end end |
現在ご覧のページの最終更新日時は2015/03/15 01:08:06です。
Copyright (C) N.Y.City ALL Rights Reserved.
Email: info[at]nycity.main.jp