8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScript文法(10) JavaScriptのオブジェクト指向プログラミング3 多態性

Last updated at Posted at 2016-09-05

この記事の内容

  • JavaScriptでオーバーライドを実現する方法
  • JavaScriptでオーバーロード(っぽいこと)を実現する方法

目次

(1) 学習環境の構築と基本的な書き方
(2) 変数とデータ型
(3) 演算子および配列
(4) 制御構文と関数
(5) 関数の応用1
(6) 関数の応用2
(7) オブジェクトの基礎
(8) JavaScriptのオブジェクト指向プログラミング1 概要
(9) JavaScriptのオブジェクト指向プログラミング2 オブジェクトの作り方と継承
(10) JavaScriptのオブジェクト指向プログラミング3 多態性 <-- この記事の内容
(11) JavaScriptのオブジェクト指向プログラミング4 カプセル化
(12) ES6の新機能について

今回扱う内容

この記事では、オブジェクト指向プログラミングの3つの特徴(継承、多態性、カプセル化)のうち、「多態性(Polymorphism)」について。当記事では、以下の内容の実現方法について触れる。

  • オーバーライド
  • オーバーロード

※ただし、「多態性」というサブタイトルをつけた記事の中でオーバーライドやオーバーロードについて触れていることに違和感を持たれるかもしれないが、多態性を実現する手段という意味合いで、このような構成にしている点、ご容赦いただきたい。

オーバーライド

オーバーライドは、親クラスのメソッドの定義内容を子供クラスで上書きすることを言う。

前回、このようなコードを扱った。

bs10_01.js
var Animal = function(name, voice) {
  this.name  = name;
  this.voice = voice;
}
Animal.prototype.bark = function() {
  console.log(this.name + "が鳴く:" + this.voice);
}

var Dog = function(name) {
  Animal.call(this, name, "ワン!");
}
Dog.prototype = new Animal;

var Cat = function(name) {
  Animal.call(this, name, "ニャー!");
}
Cat.prototype = new Animal;

var pochi = new Dog("ポチ");
var tama  = new Cat("タマ");

pochi.bark();        // ポチが鳴く:ワン!   と表示される
tama.bark();         // タマが鳴く:ニャー! と表示される

このコードを、Dog については bark メソッドをオーバーライドして、「鳴く」ではなく「吠える」と表示するようにしてみたい。以下のように記述する。

bs10_02.js
var Animal = function(name, voice) {
  this.name  = name;
  this.voice = voice;
}
Animal.prototype.bark = function() {
  console.log(this.name + "が鳴く:" + this.voice);
}

var Dog = function(name) {
  Animal.call(this, name, "ワン!");
}
Dog.prototype = new Animal;
Dog.prototype.bark = function() {
  console.log(this.name + "が吠える:" + this.voice);
}

var Cat = function(name) {
  Animal.call(this, name, "ニャー!");
}
Cat.prototype = new Animal;

var pochi = new Dog("ポチ");
var tama  = new Cat("タマ");

pochi.bark();        // ポチが吠える:ワン! と表示される
tama.bark();         // タマが鳴く:ニャー! と表示される

修正したのは、以下の部分。

Dog.prototype = new Animal;
Dog.prototype.bark = function() {
  console.log(this.name + "が吠える:" + this.voice);
}

Animalクラスのプロトタイプを参照する指定をした後に、プロトタイプの中の bark メソッドをオーバーライドしている。これにより Dogbark した場合は「吠える」と表示され、特に何もオーバーライドしていない Cat については、親の定義のまま「鳴く」と表示される。

オーバーロード

メソッドの名前は同じでも引数の数が異なっていれば、それぞれ別の処理を定義・実行できる仕組みがオーバーロードである。JavaScriptではオーバーロードの機能は備わっていないため、どうしてもオーバーロードをしたければ、引数をチェックして、その内容によって処理を振り分けるという形になる。

bs10_01.js の内容に戻る。ここの Animal オブジェクトで実装した bark メソッドは、voice プロパティに格納された文字列を鳴き声として表示するようにしていた。通常の bark はこれで良いが、引数を1つ指定して、その引数に入れた文字列を鳴き声として表示するように改良してみたい。

bs10_03.js
var Animal = function(name, voice) {
  this.name  = name;
  this.voice = voice;
}
Animal.prototype.bark = function(voice) {
  if(typeof voice === "undefined") {
   console.log(this.name + "が鳴く:" + this.voice);
  } else {
   console.log(this.name + "が鳴く:" + voice);
  }
}

var Dog = function(name) {
  Animal.call(this, name, "ワン!");
}
Dog.prototype = new Animal;

var Cat = function(name) {
  Animal.call(this, name, "ニャー!");
}
Cat.prototype = new Animal;

var pochi = new Dog("ポチ");
var tama  = new Cat("タマ");

pochi.bark();        // ポチが鳴く:ワン!   と表示される
tama.bark();         // タマが鳴く:ニャー! と表示される

pochi.bark("くーん...");    // ポチが鳴く:くーん... と表示される

変数が初期化されていなければ undefined というデータ型になる、という性質を利用して、引数が指定されていない(= undefined)か、指定されているかを typeof で調べ、処理を分けている。オーバーロードっぽいことは、このようにして実現できる。

発展的内容: カリー化

もう一つ、オーバーロードっぽいことを実現できる方法について触れておく。

ここでは、引数として与えられた2つの数を加算した結果を返す関数を考える。通常、

function add(x, y) {
  return x + y;
}

このように書けば問題ないが、あえて、このように書いたとする。

function add(x) {
  return function(y) {
    return x + y;
  }
}

add 関数に引数は1つだけ。この関数の中に匿名関数があり、そこにも引数が1つある。この関数を実行するためには、どのような指定を記述すれば良いだろうか。

答えは、以下のとおりである。

bs10_04.js
function add(x) {
  return function(y) {
    return x + y;
  }
}

console.log( add(3)(2) );    // 5 と表示される

上のコードのとおり、add(3)(2) と書く。 最初の (3) の部分で指定した数値の 3 は add 関数の x に代入される。後ろの (2) の部分で指定した数値の 2 は、add 関数の中にある匿名関数の引数 y に代入される。そのため、このような記述で問題なく実行される。

この状態から少し修正を加える。

var addFunc = add(3);

このような記述に変更する。この1文の内容は、 addFunc という変数に、 add 関数の引数 x に3を入れた状態のものを格納する、というものになる。 add 関数の計算処理は実行されるわけではない。つまり、 add(3)という記述を addFunc という文字列に置き換えた、と思ってもらえれば良い。こうすることで、この後は addFunc(2); と記述すれば add 関数の計算が実行されることになる。

bs10_05.js
function add(x) {
  return function(y) {
    return x + y;
  }
}

var addFunc = add(3);

console.log( addFunc(2) );    // 5 と表示される
console.log( addFunc(4) );    // 7 と表示される

このように、引数を分解して、分解された引数の一部を引数に取る匿名関数を元の関数の中につくることをカリー化と呼ぶ。

カリー化を利用し、引数の一部を内部の匿名関数に切り分け、残りの引数の内容を固定するような形で記述すれば、オーバーロードっぽいことが実現できる。

次回予告

カプセル化

8
6
2

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
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?