JavaScript
オブジェクト指向
ECMAScript
ポエム

オブジェクト指向が役に立った話


ワイ、会社やめたってよ

ワイ「上司くんが嫌い過ぎて会社やめてもうたわ」

ワイ「あんなプログラミングのことを何も分かってへんようなやつが指示をする立場におるから、プロジェクトが炎上すんねん。」

ワイ「クライアントにまともに質問もできひん。だから仕様がなんとなくしかエンジニアに伝わってけぇへんねん」

ワイ「何や既存の仕様を踏襲って。そればっかやん。」

ワイ「その既存の仕様の仕様書をよこせっちゅうねん」

ワイ「表面上は見えないような仕様はどないして踏襲しろっちゅうねん・・・いてまうど!」


就職活動をしないといけない

ワイ「とにかく仕事探さんとな・・・」

ワイ「いうてもワイ、学歴もなくて職歴もクソやから、書類審査で落とされんねん・・・」

ワイ「ポテンシャルはめちゃくちゃあるんやけど、アホな企業どもがワイの力を見抜けへんねん」

ワイ「あー、なんかプログラミングのテストを受けて、その結果をもとに企業はんにアピールできる就活サイトとかないんかな・・・!」

ワイ「・・・ググってみよか」

ワイ「プログラミング、テスト、転職・・・と、これで検索や!」


あった

ワイ「コーディング転職サイトquizaやと!?」

ワイ「なるほどな。プログラミングのクイズを解いて、自分のランクを上げていって、そうすると企業はんからスカウトが来るわけやな。」

ワイ「さっそく問題を解いてみるで!!!」


レベル1:サイコロ転がし


サイコロを転がして、コマが何マス進んだかを答える問題です。


ワイ「おお、面白そうやないか」


あなたはサイコロとコマを使って友人と遊んでいます。


ワイ「なるほど、ヒマなんやな」

ワイ「ワイとおんなじや」


サイコロを机に置いた状態で、友人の指示に従ってサイコロを前後に転がし、転がした際に出た目のぶんだけコマを進めます。

指示通りサイコロを転がし終わった後、コマが元の位置から何マス進んだかを答えてください。


 ワイ「なるほどなるほど」


友人からの指示は「前、後、前、前、後、後」といった形で渡されます。

「前に転がす」とはサイコロを前方に90度回転させる事を指します。

「後ろに転がす」とはサイコロを後方に90度回転させる事を指します。

転がすたびに、出ている目のぶんだけコマを進めてください。


ワイ「なるほどな、サイコロを前に90度転がして、6の目が上になってたらコマが6マス進むってことやな」


最後に、進んだマスの合計値を出力してください。


ワイ「指示通りにサイコロを回転させて、その都度コマを進めて、合計何マス進んだかを解答すんねやな」

ワイ「やったろうやないかい、試験開始!と」


コードを書いてみる

ワイ「おお、ブラウザ上にテキストエディタが出てきたで。ここでコードを書いていくんやな」

ワイ「言語はJavaScriptを選択や!」

ワイ「なんや、デフォルトでコードが1行書いてあるな」

const shiji = ["前", "前", "後", "後", "前", "後"];

ワイ「なるほどな。これが友人くんからの指示や」

ワイ「前・前・後ろ・後ろ・前・後ろ、の順でサイコロを回転させて、そのたびにマスを進めていけばええねん」

ワイ「そんで、最初はどの面が上を向いてんねやろ。仕様によると・・・」


<条件>

初期状態ではサイコロの目は以下の状態になっているものとします。

「1が上、6が下、2が前、5が後ろ、3が左、4が右」


ワイ「なるほど。ほんならまずはサイコロの目の初期状態を変数に入れて、それをいじっていく感じで行こか」

let saikoro_eyes = {

"上": 1,
"下": 6,
"前": 2,
"後": 5,
"左": 3,
"右": 4
};

ワイ「サイコロの目の情報が入った配列やから、変数名はsaikoro_eyesや!」

ワイ「何マス進んだかを覚えておく変数も作らんとな。初期値はゼロや」


script.js

let masu = 0;


ワイ「サイコロを前後に回転させると、サイコロの目の位置が変わるわけやから、そのための関数が必要やな」

ワイ「”前”と”後”に対応した関数が実行されれば良いわけやから、前回転と後ろ回転の2つの関数を持った連想配列を作れば良さそうやな」

const kaiten_functions = {

"前": saikoro_eyes => {
// サイコロの目の状態を引数として受け取って
// 前回転の処理をして
// 回転後のサイコロの目の状態を返す。
},
"後": saikoro_eyes => {
//後ろ回転バージョン
}
};

ワイ「前回転の処理はどないな感じや」

ワイ「サイコロが前に90度回転すると・・・」

ワイ「回転後にに来るのは、もともとにあった面やな」

const new_saikoro_eyes = {};

//回転後の目の状態を格納する連想配列を作成。

new_saikoro_eyes["前"] = saikoro_eyes["上"];
//上にあった面の数値を、回転後の「前」に格納。

ワイ「ほんで、回転後にに来るのは、もともと後ろにあった面やな」

new_saikoro_eyes["上"] = saikoro_eyes["後"];

ワイ「ほんで、後ろに来るのはにあった面」

ワイ「に来るのはにあった面」

ワイ「の面は変わらずや。前後の回転だけやからな」

ワイ「前回転の関数ができたで!」

const kaiten_functions = {

"前": saikoro_eyes => {
const new_saikoro_eyes = {};
new_saikoro_eyes["前"] = saikoro_eyes["上"];
new_saikoro_eyes["上"] = saikoro_eyes["後"];
new_saikoro_eyes["後"] = saikoro_eyes["下"];
new_saikoro_eyes["下"] = saikoro_eyes["前"];
new_saikoro_eyes["左"] = saikoro_eyes["左"];
new_saikoro_eyes["右"] = saikoro_eyes["右"];
return new_saikoro_eyes;
},
"後": saikoro_eyes => {
//後ろ回転バージョン
}
};

ワイ「後ろ回転は、その逆やから・・・こうやな!」

const kaiten_functions = {

"前": saikoro_eyes => {
const new_saikoro_eyes = {};
new_saikoro_eyes["前"] = saikoro_eyes["上"];
new_saikoro_eyes["上"] = saikoro_eyes["後"];
new_saikoro_eyes["後"] = saikoro_eyes["下"];
new_saikoro_eyes["下"] = saikoro_eyes["前"];
new_saikoro_eyes["左"] = saikoro_eyes["左"];
new_saikoro_eyes["右"] = saikoro_eyes["右"];
return new_saikoro_eyes;
},
"後": saikoro_eyes => {
const new_saikoro_eyes = {};
new_saikoro_eyes["後"] = saikoro_eyes["上"];
new_saikoro_eyes["下"] = saikoro_eyes["後"];
new_saikoro_eyes["前"] = saikoro_eyes["下"];
new_saikoro_eyes["上"] = saikoro_eyes["前"];
new_saikoro_eyes["左"] = saikoro_eyes["左"];
new_saikoro_eyes["右"] = saikoro_eyes["右"];
return new_saikoro_eyes;
}
};

ワイ「サイコロ回転関数ができたで!」

ワイ「あとは指示内容が入った配列、つまりshijiforEachメソッドで回してサイコロの面を更新していけばエエねん」

ワイ「サイコロを動かすたびに変数masuに数値を加算していくのも忘れずに、やな!」

shiji.forEach(houkou => {

saikoro_eyes = kaiten_functions[houkou](saikoro_eyes);
//サイコロの目の状態を更新。
//引数 houkou には"前"か"後"が入っていて、
//前回転か後ろ回転の関数が実行される。

masu += saikoro_eyes["上"];
//回転後に出ている目(サイコロの上の面)の数値をmasuに加算。
});

ワイ「最後に、合計で何マス進んだかを出力や!!!」

console.log(masu);

ワイ「どや!」


結果


結果:正解です


ワイ「よっしゃ、次はレベル2に行ったるで!!!」


レベル2:サイコロ転がし(複数編)


「レベル1:サイコロ転がし」の複数編です。

サイコロが5個に増えます。

サイコロの目の初期状態は、5個それぞれ異なります。

指示も5個分あたえられます。

コマも5つになります。

指示通りにサイコロたちを回転させ、それぞれのコマが何マス進んだか、

5回出力してください。


ワイ「!?」

ワイ「思わずドラゴンボールみたいな声出してもうたわ」

ワイ「面倒臭そうやな・・・とにかく、試験開始!と」


やってみる

ワイ「お、今回もデフォルトでなんかコードが入力されとるで」

const saikoro_infos = [

{
eyes: {"上": 1, "下": 2, "前": 3, "後": 4, "左": 5, "右": 6},
shiji: ["前", "前", "後", "後", "前", "後"]
},
{
eyes: {"上": 2, "下": 3, "前": 4, "後": 5, "左": 6, "右": 1},
shiji: ["後", "前", "後"]
},
{
eyes: {"上": 3, "下": 4, "前": 5, "後": 6, "左": 1, "右": 2},
shiji: ["後", "後", "前", "後"]
},
{
eyes: {"上": 4, "下": 5, "前": 6, "後": 1, "左": 2, "右": 3},
shiji: ["前", "後", "前", "後", "前"]
},
{
eyes: {"上": 5, "下": 6, "前": 1, "後": 2, "左": 3, "右": 4},
shiji: ["前", "前", "前", "後", "後", "前", "後"]
}
];

ワイ「おお、今回は5個分のサイコロの目と、それぞれどう回転させるかの指示の情報がsaikoro_infosいう定数に入ってんねやな」

ワイ「えーと、それぞれのサイコロを指示通りに回転させて・・・そのとおりに、それぞれのコマを進めていくには・・・」

ワイ「めんどくさ・・・」

ワイ「わけわからんくなってきたわ・・・」


現実逃避

ワイ「もっとこう、それぞれのコマ君たちが自分で指示書を読んで、サイコロを動かして、出た目のぶんだけ進んでってくれたらええのにな・・・」

ワイ「コマ君たちは自分が今何マス目におるか、自分のサイコロが今どっちを向いてるか、みたいな情報を記憶してくれてんねん」

ワイ「ほんで、サイコロを振る、とか、進む、とかいう技をもってんねん」

ワイ「そういった便利なコマオブジェクトを5個作ってくれるオブジェクトメーカーみたいなんがあったらええのにな・・・」

ワイ「・・・」

ワイ「そんな便利な機能あるわけないか・・・」


クラスを使おう

ワイ「!!」

ワイ「そうや、クラスや!」

ワイ「たしか、クラスを使えばオブジェクトの持つべき機能を定義して、そこからおんなじ種類のオブジェクトを何個も量産できんねや!」


どんなクラスを作ろう

ワイ「ワイのイメージでは、コマ君が5体おんねや」

ワイ「コマ君たちはそれぞれ1個ずつサイコロと指示書をもってんねや」

ワイ「ほんで、指示書のとおりにサイコロを回転させて、出た目のぶんだけ毎回マスを進むんや」


Komaクラスを作ってみる

ワイ「ほんならまず、コマのクラスを作るで」

ワイ「・・・初めてやから、どう作ってええか分からへん・・・」

ワイ「ワイは、何を作りたいんや・・・コマ君を作って、どんな事をさせたいんや・・・」


まずコマ君をどう動かしたいかを考えてみる。

ワイ「コマ君を5体生成して、それぞれ自分の指示書を見ながらサイコロを振って、進んでって欲しいねや」

ワイ「イメージだけでコードにするとこんな感じや」

//サイコロと方向指示の情報を元に、5体のコマ君が入った配列を生成。

const komas = saikoro_infos.map(saikoro_info => new Koma(saikoro_info));

//コマ君たちをforEachで回す。
komas.forEach(koma => {
//コマ君は指示書に書いてあるぶんだけ、
koma.shiji.forEach(houkou => {
koma.saikoro_kaiten(houkou);
//サイコロを回転させ、

koma.susumu();
//マスを進む。
});
console.log(koma.genzai_masu());
//進み終わったら、現在のマスを出力。
});

ワイ「こんな感じでコマ君を使いたいんや」 


Komaクラスを作ってみる(再)

ワイ「コマ君にどう動いてもらいたいかを考えたら、少しは要件が分かってきたで・・・」

ワイ「クラスにはプロパティメソッドがあんねや」

ワイ「プロパティいうんはコマ君についての情報や状態、つまりコマ君が持ってるもんを格納するためのもんや」

ワイ「コマくんが持ってるのは・・・」


  • サイコロ

  • 指示書

  • 何マス目にいるかの情報

ワイ「こんな感じやな」

ワイ「あとメソッドも作らなあかん」

ワイ「メソッドいうんは、コマ君のもってる技や」

ワイ「今回必要な技は・・・」


  • サイコロを回転させる

  • 出た目の文だけマスを進む

  • いま何マス目にいるかを答える

ワイ「こんな感じやな」

ワイ「それをコードにすると・・・」

class Koma{

//プロパティはコンストラクタの中で設定する。
constructor(saikoro_info){
this.shiji = saikoro_info.shiji;
//方向指示の情報をshijiというプロパティに格納。

this.saikoro = "サイコロの情報(デフォルトの目の状態とか)";
//サイコロの情報をsaikoroプロパティに格納。

this.masu = 0;
//何マス目にいるかを保存しておくプロパティ。
}

//サイコロを回転させるメソッド
saikoro_kaiten(houkou){
this.saikoro.kaiten(houkou);
//自身の持つサイコロに「回転せえや!」と指示。
}
susumu(){
this.masu += this.saikoro.get_eye();
//自身の持つサイコロに「いま出てる目を教えてくれや!」と指示し、
//教えてもらった数の分だけマスを進む。
}
output_masu(){
console.log(this.masu);
//現在のマスの数値を出力。
}
}

ワイ「こんな感じか・・・なんや、サイコロはどうやってプロパティにすればええねん」

ワイ「サイコロは、目の情報を持ってて、あと前回転と後ろ回転ができんねん・・・」

ワイ「!!」

ワイ「サイコロも、自分に関するデータと、技みたいなもんを持ってんねや・・・」

ワイ「自分に関するデータ=プロパティ、技=メソッドや!」

ワイ「サイコロもクラスにして、サイコロオブジェクトを生成すればええねや!」


Saikoroクラスを作ってみる

ワイ「Saikoroクラスは・・・」

saikoro = new Saikoro(saikoro_info.eyes);

ワイ「こないな感じで、サイコロの目の情報を渡したら、回転機能を備えたサイコロオブジェクトが返ってくる・・・」

ワイ「そんな感じにしたいねや」

ワイ「ということは・・・」

class Saikoro{

constructor(saikoro_eyes){
this.eyes = saikoro_eyes;
//引数でもらった「サイの目のデフォルト位置」を記憶。

this.func_names = {
"前": "mae_kaiten",
"後": "ushiro_kaiten"
};
//前と後、それぞれの関数が実行されるように
//関数名の連想配列を作成。
}

//サイコロ自身を回転させるメソッド
kaiten(houkou){
const func_name = this.func_names[houkou];
//houkou(方向)が前ならmae_kaiten、
//後ろだったらushiro_kaitenがfunc_nameにセットされる。

this[func_name]();
//mae_kaitenまたはushiro_kaitenメソッドを実行。
}

//前回転メソッド
mae_kaiten(){
const new_saikoro_eyes = {};
new_saikoro_eyes["前"] = this.eyes["上"];
new_saikoro_eyes["上"] = this.eyes["後"];
new_saikoro_eyes["後"] = this.eyes["下"];
new_saikoro_eyes["下"] = this.eyes["前"];
new_saikoro_eyes["左"] = this.eyes["左"];
new_saikoro_eyes["右"] = this.eyes["右"];
this.eyes = new_saikoro_eyes;
}

//後ろ回転メソッド
ushiro_kaiten(){
const new_saikoro_eyes = {};
new_saikoro_eyes["後"] = this.eyes["上"];
new_saikoro_eyes["下"] = this.eyes["後"];
new_saikoro_eyes["前"] = this.eyes["下"];
new_saikoro_eyes["上"] = this.eyes["前"];
new_saikoro_eyes["左"] = this.eyes["左"];
new_saikoro_eyes["右"] = this.eyes["右"];
this.eyes = new_saikoro_eyes;
}

//いま出ている目を教えてくれるメソッド
get_eye(){
return this.eyes["上"];
}
}

ワイ「Saikoroクラスはこないな感じや!」


Komaクラスに戻る

ワイ「Komaクラスのコンストラクタの中で、そのコマ君の持つサイコロの情報を入れとる部分があったな」

this.saikoro = "サイコロの情報(デフォルトの目の状態とか)";

ワイ「このsaikoroいうプロパティに、Saikoroクラスのインスタンスを」

ワイ「ぶち込んでやったらええねん」

this.saikoro = new Saikoro(saikoro_info.eyes);

//サイコロオブジェクトを生成して、saikoroプロパティに格納。

 ワイ「こうや!」


提出コード

const saikoro_infos = [

{
eyes: {"上": 1, "下": 2, "前": 3, "後": 4, "左": 5, "右": 6},
shiji: ["前", "前", "後", "後", "前", "後"]
},
{
eyes: {"上": 2, "下": 3, "前": 4, "後": 5, "左": 6, "右": 1},
shiji: ["後", "前", "後"]
},
{
eyes: {"上": 3, "下": 4, "前": 5, "後": 6, "左": 1, "右": 2},
shiji: ["後", "後", "前", "後"]
},
{
eyes: {"上": 4, "下": 5, "前": 6, "後": 1, "左": 2, "右": 3},
shiji: ["前", "後", "前", "後", "前"]
},
{
eyes: {"上": 5, "下": 6, "前": 1, "後": 2, "左": 3, "右": 4},
shiji: ["前", "前", "前", "後", "後", "前", "後"]
}
];

class Saikoro{
constructor(saikoro_eyes){
this.eyes = saikoro_eyes;
//引数でもらったサイの目のデフォルト位置を記憶。

this.func_names = {
"前": "mae_kaiten",
"後": "ushiro_kaiten"
};
//前と後、それぞれの関数が実行されるように
//関数名の連想配列を作成。
}

//サイコロ自身を回転させるメソッド
kaiten(houkou){
const func_name = this.func_names[houkou];
//houkou(方向)が前ならmae_kaiten、
//後ろだったらushiro_kaitenがfunc_nameにセットされる。

this[func_name]();
//mae_kaitenまたはushiro_kaitenメソッドを実行。
}

//前回転メソッド
mae_kaiten(){
const new_saikoro_eyes = {};
new_saikoro_eyes["前"] = this.eyes["上"];
new_saikoro_eyes["上"] = this.eyes["後"];
new_saikoro_eyes["後"] = this.eyes["下"];
new_saikoro_eyes["下"] = this.eyes["前"];
new_saikoro_eyes["左"] = this.eyes["左"];
new_saikoro_eyes["右"] = this.eyes["右"];
this.eyes = new_saikoro_eyes;
}

//後ろ回転メソッド
ushiro_kaiten(){
const new_saikoro_eyes = {};
new_saikoro_eyes["後"] = this.eyes["上"];
new_saikoro_eyes["下"] = this.eyes["後"];
new_saikoro_eyes["前"] = this.eyes["下"];
new_saikoro_eyes["上"] = this.eyes["前"];
new_saikoro_eyes["左"] = this.eyes["左"];
new_saikoro_eyes["右"] = this.eyes["右"];
this.eyes = new_saikoro_eyes;
}

//いま出ている目を教えてくれるメソッド
get_eye(){
return this.eyes["上"];
}
}

class Koma{
constructor(saikoro_info){
this.shiji = saikoro_info.shiji;
//方向指示の情報をshijiプロパティに格納。

this.saikoro = new Saikoro(saikoro_info.eyes);
//サイコロオブジェクトを生成して、saikoroプロパティに格納。

this.masu = 0;
//何マス目にいるかを保存しておくプロパティ。
}

//サイコロを回転させるメソッド
saikoro_kaiten(houkou){
this.saikoro.kaiten(houkou);
//自身の持つサイコロに「回転せえや!」と指示。
}
susumu(){
this.masu += this.saikoro.get_eye();
//自身の持つサイコロに「今の目を教えてぇな!」と指示し、
//教えてもらった数の分だけマスを進む。
}
genzai_masu(){
return this.masu;
//現在のマスの数値を返す。
}
}

//サイコロと方向指示の情報を元に、5体のコマ君が入った配列を生成。
const komas = saikoro_infos.map(saikoro_info => new Koma(saikoro_info));

//コマ君たちをforEachで回す。
komas.forEach(koma => {
//コマ君は指示書に書いてあるぶんだけ、
koma.shiji.forEach(houkou => {
koma.saikoro_kaiten(houkou);
//サイコロを回転させ、

koma.susumu();
//マスを進む。
});
console.log(koma.genzai_masu());
//進み終わったら、現在のマスを出力。
});


結果


結果:正解です


ワイ「よっしゃ!おおきにやで!」


晩酌タイム

ワイ「もう15時やないか・・・酒飲む時間やで」

ワイ「レベル2の問題、ホンマむずかしかったなぁ・・・」

ワイ「サイコロが5個になったときは死のうかと思ったわ・・・」

ワイ「サイコロの目の情報も5倍、どう回転させるかの指示も5倍、マスの位置の管理も5倍」

ワイ「こんなん、普通に配列とかで管理してたら、訳わからんくなるで」

ワイ「クラス様様やで」


クラスを見直した

ワイ「クラスって、書き方だけは勉強してたんやけど今まで何に使ってええか分からへんかったんや」

ワイ「人間クラスから生成した太郎オブジェクトが、自己紹介メソッドで」

ワイ「こんにちは、僕は太郎です!」

ワイ「って挨拶できる感じなのは知ってたんやけど」

ワイ「太郎があいさつして何がうれしいねん!

ワイ「太郎は何を便利にしてくれんねん!

ワイ「と思ってたんや」

ワイ「でも、便利なもんやったんやな」

ワイ「そもそもクラスって誰が考えたんやろ・・・」

ワイ「ググってみよか・・・」


クラスとオブジェクトの歴史

ワイ「クラスとオブジェクトっていう機能は、もともとSimulaっていう言語で初めて実装されたんか」

ワイ「Simulaはその名の通り、現実世界をシミュレート(simulate)するために作られた言語なんか」

ワイ「せやから、色んな物体の状態・情報・特性・・・」

ワイ「その物体がどんな動きをするか・・・」

ワイ「そういう事を定義できるクラス構文が生まれたんやな」

ワイ「ほー、そんときはまだオブジェクト指向いう言葉すら無かったんか」

ワイ「Simulaハンパないな」


なぜクラスを使うと分かりやすいか

ワイ「もともと現実世界をシミュレートするために作られた構文だけあって、人間が世界を認識するときの考え方に基づいてんねんな」

ワイ「せやから、人間にとって理解しやすい書き方になんねん」

ワイ「サイコロたちの目を格納する配列、それぞれのコマの位置を格納するための配列・・・ってそれぞれの情報ごとに管理するんやなくて、」

ワイ「コマ君が、サイコロを持ってんねん」

ワイ「自分のための指示書も持ってんねん」

ワイ「そんで、サイコロを回転させて、マスを進んでくれんねん」

ワイ「そういったイメージを、割とそのままの形でコードに出来んねん」

ワイ「せやから、そのイメージを頭の中にずっと置いておく労力が減るねん」

ワイ「ワイのようなメモリの少ない脳みそでもプログラミングが捗るねんな」


クラスを使うとコードも読みやすく

ワイ「せやからコード自体も読みやすくなるな」

ワイ「コマに関する情報や動作はKomaクラスの中に書かれる」

ワイ「サイコロに関する情報や動作はSaikoroクラスの中に書かれる」

ワイ「コードが知らんうちにグループ分けされるんや」

ワイ「ワイのようなザコーダーやと、自分の書いたコードでも」

ワイ「コマの位置を格納した配列はどれやったけ〜?

ワイ「ってなんねんけど、」

ワイ「Komaクラスの中にまとまってれば安心や」


ガンガン使っていこう・・・?

ワイ「せっかくクラス構文の使い道っぽいのに気づくことができたんやから、どんどん使っていくで!」

ワイ「でも普段Webサイト作る時に、どんな場面でJavaScriptのクラスを使うたらええんやろな・・・」

ワイ「ブラウザにはDOMっちゅう機能があって、ページ内のタグ1たちをJSで操作できるようにしてくれてんねん」

ワイ「だから自分でクラスを考えなくても、タグたちを好きなように操作できんねん」

ワイ「タグたちを、いろんなプロパティやメソッドを持ったオブジェクトとして使えんねん」

ワイ「DOMもオブジェクト指向に基づいて作られてたんやな・・・」

ワイ「Document Object Modelいうくらいやもんな」

ワイ「ほんならワイがクラス書く場面ないやん」

ワイ「ないやん・・・」





  1. 正しくは「要素」ですね。