IORI online School

JavaScript、html、css の無料学習サイト

JavaScriptで学ぶオブジェクト指向

JavaScriptで学ぶオブジェクト指向

本講座ではJavaScriptを利用して、オブジェクト指向を学習する。 オブジェクト指向はプログラミング界隈の一般的な用語であり、 JavaScript固有の用語ではない。 また本講座はJavaScriptオブジェクト指向を学習するのではなく、 プログラミング一般で言われるオブジェクト指向を学習する。

学習順序を解説する。 オブジェクト指向を解説する前に、 プログラムの実例をご覧頂く。 最初に非オブジェクト指向で作ったプログラムを見る。 これを批評した後、オブジェクト指向プログラミングで 作り変える。実際のプログラムを見て、イメージを持った後、 オブジェクト指向の概念を解説する。

オブジェクト指向のプログラム

sample.htmlの画面

[メッセージ]

  • message A
  • message B
  • message C
  • message D

製造するプログラムを解説する。 文字列を4つ画面に表示するプログラムである。 会員サイトのメッセージ機能をイメージしてほしい。 会員サイトにログインすると、 メッセージボックスにメッセージが表示される。 そのメッセージの文字列を画面に表示するプログラムを作る。 まず、sample.htmlを見る。

001<!DOCTYPE html><html lang="ja">
002<head><meta charset="UTF-8">
003<script type="text/javascript" src="app.js" >
004</script>
005</head>
006<body>
007  
008  <p>[メッセージ]</p>
009  <ul id="messages"></ul>
010  
011</body>
012</html>

sample.htmlファイルである。 本講座では、このファイルを変更せずに終始利用する。 プログラムを解説する。 3行目で、app.jsファイルを読み込む。 app.jsファイルで、画面に4つの文字列を表示する。 9行目にulタグを設置する。 HTMLでは文字列を表示していないが、 JavaScriptを利用して、このulタグにメッセージを表示する。 ulタグに、文字列を表示したliタグを追加する形で、 4つの文字列を画面に表示する。 では、app.jsを見る。

001'use strict'
002//表示するメッセージの文字列
003const messages = [
004    " message A" ," message B" ,
005    " message C" ," message D" ,
006];
007//ブラウザ読み込み完了時に実行
008window.onload = () => {
009
010  const ul =
011    document.getElementById("messages");
012  
013  //メッセージを表示
014  messages.forEach ( ( message ) => {
015    
016    //liタグを作成
017    const li = document.createElement("li");
018    
019    //文字列設定
020    li.textContent = message ;
021    
022    //ulタグへ追加
023    ul.append ( li ) ;
024  });
025}

3行目から6行目がメッセージの内容である。 メッセージを配列で持ち、それぞれのメッセージを ulタグに追加する。 今回、文字列「mesage A」「mesage B」 「mesage C」「mesage D」が画面に表示する。 では、プログラムを詳解する。 8行目で、ブラウザの読み込み完了時に実行する 関数を登録する。

010  const ul =
011    document.getElementById("messages");
012  
013  //メッセージを表示
014  messages.forEach ( ( message ) => {
015    
016    //liタグを作成
017    const li = document.createElement("li");
018    
019    //文字列設定
020    li.textContent = message ;
021    
022    //ulタグへ追加
023    ul.append ( li ) ;
024  });

ブラウザ読み込み完了時に実行するプログラムである。 メッセージを画面に表示する処理をする。 10行目から解説する。HTMLからidの値がmessagesの オブジェクトを取得して変数ulへ代入する。 idの値がmessagesのタグはulタグである。 ulタグのオブジェクトが変数ulに代入する。 14行目でforEach()関数を利用して、 messages配列の要素を全て取り出す。 messages配列の要素は、メッセージボックスに表示する文字列である。 文字列が、14行目の変数messageに入る。 17行目でliタグを作成して、 20行目で、liタグに文字列を代入する。 これで、messages配列に入った文字列がliタグに表示する。 23行目でliタグをulタグに追加する。 解説は以上である。 画面の表示を見る。

sample.htmlの画面

[メッセージ]

  • message A
  • message B
  • message C
  • message D

4つの文字列が表示する。プログラムの解説は以上である。 さて突然ではあるが、 このプログラムは良いプログラムだろうか?

良いプログラム 拡張性と柔軟性がある

プログラムを製造する時、目の前のプログラムを作る事に 専念しがちである。 しかし、それは誤っている。 作るべきプログラムは、当然作る。 さらに、プログラムの機能追加、 改修に対応できなくてはならない。 プログラムを作って終わり、というわけにはいかない。 当然その後、機能追加や改修作業がある。 速やかに機能追加や改修作業ができる プログラムが、良いプログラムである。

オブジェクト指向プログラミング プログラムに拡張性と柔軟性を持たせるための プログラミング技法

オブジェクト指向プログラミングは、プログラムに拡張性と柔軟性を与える。

では、話を元へ戻す。 現在製造したプログラムは非オブジェクト指向で作っている。 これに機能を追加する。

sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C
  • message D[重要]

各メッセージに、重要度を設定する。 重要度の高いメッセージは、目立つように表示する。 今回、「message B」と「message D」の重要度を 仮に「10」とする。そして、重要度が「10」 のメッセージは、メッセージの後ろに「重要」と表記する。 では、app.jsファイルを見る(sample.htmlは変更しない)。

001'use strict'
002//メッセージ
003const messages = [
004  [ "message A" , "0" ] ,
005  [ "message B" , "10" ] ,
006  [ "message C" , "0" ] ,
007  [ "message D" , "10" ] ,
008];
009//ブラウザ読み込み完了時に実行
010window.onload = () => {
011
012  const ul =
013    document.getElementById ("messages");
014  
015  //メッセージを表示
016  messages.forEach ( ( value ) => {
017    
018    //liタグを作成
019    const li = document.createElement("li");
020    
021    //文字列設定
022    if ( value [ 1 ] === "10" ){
023      li.textContent = value[0] + "[重要]";
024    } else {
025      li.textContent = value[0] ;
026    }
027    
028    //ulタグへ追加
029    ul.append ( li ) ;
030  });
031}

まず、4行目から7行目でメッセージを作成する。

002//メッセージ
003const messages = [
004  [ "message A" , "0" ] ,
005  [ "message B" , "10" ] ,
006  [ "message C" , "0" ] ,
007  [ "message D" , "10" ] ,
008];

前回のプログラムでは、メッセージは文字列であった。 こんかい、メッセージを配列で持つ。 各メッセージの配列[0]に文字列を代入して、 配列[1]に重要度を代入する。 「message B」と「message D」の重要度を「10」にして、 「message A」と「message C」の重要度は「0」にした。 では、続きを見る。

012  const ul =
013    document.getElementById ("messages");
014  
015  //メッセージを表示
016  messages.forEach ( ( value ) => {
017    
018    //liタグを作成
019    const li = document.createElement("li");
020    
021    //文字列設定
022    if ( value [ 1 ] === "10" ){
023      li.textContent = value[0] + "[重要]";
024    } else {
025      li.textContent = value[0] ;
026    }
027    
028    //ulタグへ追加
029    ul.append ( li ) ;
030  });

ブラウザの読み込みが完了すると、このプログラムが実行する。 12行目でulタグオブジェクトを取得した後、 16行目で配列のforEach()関数を実行する。 16行目の引数valueにメッセージが代入される。 19行目でliタグを作成する。 22行目が問題のプログラムである。 こんかい、重要度により表示する文字列が異なる。 その分岐処理を22行目のif文で実行する。 22行目で変数valueのインデックス「1」を確認する。 変数valueはメッセージであり、インデックス「1」に メッセージの重要度が入っている。 インデックス「1」の値が「10」の場合、 23行目が実行する。 変数valueのインデックス「0」にメッセージの文字列が代入されている。 メッセージと文字列[重要]が文字列結合して、liタグに表示する。 変数valueのインデックス「1」の値が「10」以外の場合、 elseブロックが実行する。 elseブロックで、メッセージをliタグに表示する。 解説は以上である。画面をもう一度見る。

sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C
  • message D[重要]

重要度が高い 「message B」と「message D」に、文字列[重要]が表示する。 今回のプログラムは、 「if文により、表示する方法を分岐したプログラム」と言える。

このプログラムは非オブジェクト指向と言える。 問題の所在は明らかである。if文である。 たとえば、今後重要度の種別が増えた時、 if文にどんどん条件文を追加する必要がある。

あるいは、今後メッセージを表示する画面を増やすと仮定する。 メッセージを表示する画面を増やすたびに、 このif文の分岐処理を記述する必要がある。 if文が悪いとは思わない。 多かれ少なかれif文を記述する必要がある。 しかし、不必要に同じようなif文を至る所で記述する事は、 メンテナンス性が低いと言える。 このプログラムをオブジェクト指向プログラムへ変更する。

オブジェクト指向プログラミング

オブジェクト指向プログラミングでは、 クラスを作成する。

クラス設計 機能によりクラスを分割する

オブジェクト指向というと、「モノ」というイメージがある。 しかし実際のプログラムでは「モノ」は存在しない。 存在するものは、「機能」である。

アプリケーションを製造する前に、 通常、アプリケーションで必要となる機能を列挙する。 そして、その機能を担当するクラスを製造する。 つまり、機能ごとにクラスを製造する。これが普通である。 では、今回のプログラムで必要となるクラスは何か? こんかいは、Messageクラスを作成する。 Messageクラスはメッセージを管理するクラスである。 では、オブジェクト指向でプログラムを書く。

sample.htmlの画面

[メッセージ]

  • message A
  • message B
  • message C
  • message D

はじめに製造する画面は、文字列が4つ表示する画面である。 では、プログラムを見る。

001'use strict'
002//メッセージクラス
003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011//メッセージ
012const messages = [
013  new Message ( "message A" ) ,
014  new Message ( "message B" ) ,
015  new Message ( "message C" ) ,
016  new Message ( "message D" ) ,
017];
018//ブラウザ読み込み完了時に実行
019window.onload = () => {
020
021  const ul =
022    document.getElementById("messages");
023  
024  //メッセージを表示
025  messages.forEach ( ( value ) => {
026    
027    //liタグを作成
028    const li = document.createElement("li");
029    
030    //文字列設定
031    li.textContent = value.getLabel ();
032    
033    //ulタグへ追加
034    ul.append ( li ) ;
035  });
036}

長くもなく、短くも無いプログラムである。 では、順番に解説する。

002//メッセージクラス
003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}

Messageクラスを定義する。 4行目のコンストラクタで、メッセージの文字列を受け取る。 7行目でgetLabel()メソッドを定義する。 getLabel()メソッドを実行すると、 画面に表示するメッセージを戻す。 解説を続ける。

003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011//メッセージ
012const messages = [
013  new Message ( "message A" ) ,
014  new Message ( "message B" ) ,
015  new Message ( "message C" ) ,
016  new Message ( "message D" ) ,
017];

12行目でメッセージ配列を作成する。 配列の要素は、Messageインスタンスとする。 Messageクラスをインスタンス化する時、 画面に表示する文字列を渡す。 この文字列は、Messageクラスのコンストラクタの処理により、 _titleフィールドに代入される(5行目の処理)。 もし、7行目のgetLabel()メソッドを実行すると、 ここで渡した文字列が、戻り値として戻る。

021  const ul =
022    document.getElementById("messages");
023  
024  //メッセージを表示
025  messages.forEach ( ( value ) => {
026    
027    //liタグを作成
028    const li = document.createElement("li");
029    
030    //文字列設定
031    li.textContent = value.getLabel ();
032    
033    //ulタグへ追加
034    ul.append ( li ) ;
035  });

ブラウザ読み込み完了時に実行するプログラムである。 21行目でulタグオブジェクトを取得する。 25行目でメッセージ配列のforEach()関数を実行する。 28行目でliタグを生成して、 変数liへ代入する。 31行目で、変数valueが参照するMessageインスタンスに対して、 getLabel()関数を実行する。

003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}

getLabel()関数は、コンストラクタで渡された文字列を戻す。 例えば、文字列「message A」や「message B」などである。

021  const ul =
022    document.getElementById("messages");
023  
024  //メッセージを表示
025  messages.forEach ( ( value ) => {
026    
027    //liタグを作成
028    const li = document.createElement("li");
029    
030    //文字列設定
031    li.textContent = value.getLabel ();
032    
033    //ulタグへ追加
034    ul.append ( li ) ;
035  });

31行目でMessageインスタンスのgetLabel()メソッドを実行して、 戻り値の文字列を、liタグに表示する。

sample.htmlの画面

[メッセージ]

  • message A
  • message B
  • message C
  • message D

画面には、このように表示する。

オブジェクト指向プログラムを拡張する

プログラムを拡張する。

sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C
  • message D[重要]

「message B」と「message D」に文字列[重要]を表示する。 非オブジェクト指向プログラムの場合、 if文を利用して表示するメッセージを変更していた。 オブジェクト指向プログラムだと、どうだろうか?

app.jsを見る。

001'use strict'
002//メッセージクラス
003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011class Message10 extends Message {
012  constructor ( title ) {
013    super ( title ) ;
014  }
015  getLabel () {
016    return this._title + "[重要]";
017  }
018}
019//メッセージ
020const messages = [
021  new Message ( "message A" ) ,
022  new Message10 ( "message B" ) ,
023  new Message ( "message C" ) ,
024  new Message10 ( "message D" ) ,
025];
026//ブラウザ読み込み完了時に実行
027window.onload = () => {
028
029  const ul =
030    document.getElementById("messages");
031  
032  //メッセージを表示
033  messages.forEach ( ( value ) => {
034    
035    //liタグを作成
036    const li = document.createElement("li");
037    
038    //文字列設定
039    li.textContent = value.getLabel ();
040    
041    //ulタグへ追加
042    ul.append ( li ) ;
043  });
044}

if文が消えた。クラスから見る。

003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011class Message10 extends Message {
012  constructor ( title ) {
013    super ( title ) ;
014  }
015  getLabel () {
016    return this._title + "[重要]";
017  }
018}

Messageクラスに変更はない。 今回、Message10クラスを新規作成した。 Message10クラスは重要度が「10」のメッセージを表現したクラスである。 まず、11行目でMessageクラスを継承する。 13行目で、親クラスのコンストラクタを実行する。 15行目でgetLabel()メソッドを定義する。 getLabel()メソッドを実行すると、 コンストラクタで与えられた文字列に、 文字列[重要]を結合して戻す。 MessageクラスとMessage10クラスのgetLabel()メソッドは、 メソッド名は同じだが、実行結果は異なる。

020const messages = [
021  new Message ( "message A" ) ,
022  new Message10 ( "message B" ) ,
023  new Message ( "message C" ) ,
024  new Message10 ( "message D" ) ,
025];

メッセージ配列である。 「message A」と「message C」はMessageのインスタンスを利用する。 「message B」と「message D」はMessage10のインスタンスを利用する。 なぜなら、「message B」と「message D」は重要度が高いからである。 これで、メッセージ配列の作成が完了した。 では、メッセージ配列を利用する。

029  const ul =
030    document.getElementById("messages");
031  
032  //メッセージを表示
033  messages.forEach ( ( value ) => {
034    
035    //liタグを作成
036    const li = document.createElement("li");
037    
038    //文字列設定
039    li.textContent = value.getLabel ();
040    
041    //ulタグへ追加
042    ul.append ( li ) ;
043  });

ブラウザ読み込み完了時に実行するプログラムである。 プログラムに変更はない。 29行目でulタグオブジェクトを取得する。 33行目でメッセージ配列のforEach()関数を実行する。 36行目でliタグを作成する。 39行目でgetLabel()メソッドを実行する。 いま、変数valueにメッセージ配列の要素が入っている。

020const messages = [
021  new Message ( "message A" ) ,
022  new Message10 ( "message B" ) ,
023  new Message ( "message C" ) ,
024  new Message10 ( "message D" ) ,
025];

メッセージ配列には、MessageインスタンスとMessage10インスタンスが 入っている。

032  //メッセージを表示
033  messages.forEach ( ( value ) => {
034    
035    //liタグを作成
036    const li = document.createElement("li");
037    
038    //文字列設定
039    li.textContent = value.getLabel ();
040    
041    //ulタグへ追加
042    ul.append ( li ) ;
043  });

33行目の変数valueにメッセージ配列の要素が入る。 変数valueの値は、MessageインスタンスかMessage10インスタンスである。 39行目で変数valueが参照するインスタンスに対して、 getLabel()メソッドを実行する。

003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011class Message10 extends Message {
012  constructor ( title ) {
013    super ( title ) ;
014  }
015  getLabel () {
016    return this._title + "[重要]";
017  }
018}

変数valueが参照するインスタンスがMessageインスタンスの場合、 getLabel()メソッドを実行すると、 コンストラクタに渡した文字列が戻る。 変数valueが参照するインスタンスがMessage10インスタンスの場合、 getLabel()メソッドを実行すると、 コンストラクタに渡した文字列に文字列[重要]が結合して戻る。

メッセージ インスタンス getLabel()メソッドの
戻り値
message A Message "message A"
message B Message10 "message B[重要]"
message C Message "message C"
message D Message10 "message D[重要]"

各メッセージとgetLabel()メソッドの戻り値をまとめた。 変数valueインスタンスにより、getLabel()メソッドの戻り値が異なる。

032  //メッセージを表示
033  messages.forEach ( ( value ) => {
034    
035    //liタグを作成
036    const li = document.createElement("li");
037    
038    //文字列設定
039    li.textContent = value.getLabel ();
040    
041    //ulタグへ追加
042    ul.append ( li ) ;
043  });

39行目で、getLabel()メソッドの戻り値をliタグに表示する。

sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C
  • message D[重要]

このような表示になる。

オブジェクト指向プログラムをさらに拡張

オブジェクト指向へ変更すると、 プログラムからif文が消えた。 オブジェクト指向とif文に関係性は無いが、 オブジェクト指向プログラムはif文が少ない傾向がある。 では、さらに拡張する。

sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C[確認]
  • message D[重要]

重要度を増やす。 今回重要度のレベル「確認」を追加する。 「message C」の後ろに、文字列[確認]を結合する。

重要度が異なるメッセージが増えた形である。 この場合、どのように対応するだろうか? if文だろうか?いいえ、違います。 メッセージの種類が増えたので、Messageクラスを増やす。

001'use strict'
002//メッセージクラス
003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011class Message5 extends Message {
012  constructor ( title ) {
013    super ( title ) ;
014  }
015  getLabel () {
016    return this._title + "[確認]";
017  }
018}
019class Message10 extends Message {
020  constructor ( title ) {
021    super ( title ) ;
022  }
023  getLabel () {
024    return this._title + "[重要]";
025  }
026}
027//メッセージ
028const messages = [
029  new Message ( "message A" ) ,
030  new Message10 ( "message B" ) ,
031  new Message5 ( "message C" ) ,
032  new Message10 ( "message D" ) ,
033];
034//ブラウザ読み込み完了時に実行
035window.onload = () => {
036
037  const ul =
038    document.getElementById("messages");
039  
040  //メッセージを表示
041  messages.forEach ( ( value ) => {
042    
043    //liタグを作成
044    const li = document.createElement("li");
045    
046    //文字列設定
047    li.textContent = value.getLabel ();
048    
049    //ulタグへ追加
050    ul.append ( li ) ;
051  });
052}

ブラウザ読み込み完了時のプログラムに変更はない。 では、クラスを確認する。

003class Message {
004  constructor ( title ) {
005    this._title = title ;
006  }
007  getLabel () {
008    return this._title ;
009  }
010}
011class Message5 extends Message {
012  constructor ( title ) {
013    super ( title ) ;
014  }
015  getLabel () {
016    return this._title + "[確認]";
017  }
018}
019class Message10 extends Message {
020  constructor ( title ) {
021    super ( title ) ;
022  }
023  getLabel () {
024    return this._title + "[重要]";
025  }
026}

重要度に従ってMessageクラスを3つ作成した。 今回追加したクラスはMessage5である。 11行目でMessageクラスを継承して、Message5クラスを定義する。 15行目でgetLabel()メソッドを再定義する。 getLabel()メソッドを実行すると、 コンストラクタで受け取った文字列に、文字列[確認]を結合して戻す。 メッセージ配列を確認する。

028const messages = [
029  new Message ( "message A" ) ,
030  new Message10 ( "message B" ) ,
031  new Message5 ( "message C" ) ,
032  new Message10 ( "message D" ) ,
033];

メッセージ配列である。 31行目で文字列「message C」を引数に渡して、Message5インスタンスを 配列に代入する。

037  const ul =
038    document.getElementById("messages");
039  
040  //メッセージを表示
041  messages.forEach ( ( value ) => {
042    
043    //liタグを作成
044    const li = document.createElement("li");
045    
046    //文字列設定
047    li.textContent = value.getLabel ();
048    
049    //ulタグへ追加
050    ul.append ( li ) ;
051  });

ブラウザ読み込み完了時に実行するプログラムである。 変更はない。 47行目の変数valueには、メッセージ配列の要素が入る。 変数valueが参照するインスタンスに対して、getLabel()メソッドを実行すると、 参照するインスタンスにより、戻り値が異なる。

メッセージ インスタンス getLabel()メソッドの
戻り値
message A Message "message A"
message B Message10 "message B[重要]"
message C Message5 "message C[確認]"
message D Message10 "message D[重要]"
sample.htmlの画面

[メッセージ]

  • message A
  • message B[重要]
  • message C[確認]
  • message D[重要]

このような表示になる。

解説は以上である。