はじめに
オブジェクト指向エクササイズのルール(ES2015+用に一部改変)
「オブジェクト指向エクササイズ」は、9つのルールからなりますが、ES2015+用に一部制約を緩めています。
- 1つのメソッドにつきインデントは1段階までにすること
- else 句を使用しないこと
- すべてのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
=> (ES2015+用追記)但し、thisの後ろのドットはカウントしない - 名前を省略しないこと
- すべてのエンティティを小さくすること [補足: 50行を超えるクラス、10ファイルを超えるパケッージを作らない]
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- Getter, Setter, プロパティを使用しないこと
使用する題材
ドットインストールのJavaScriptでおみくじを作ろうのプログラムをベースにします。
動作概要
おみくじは、乱数を使ってランダムに表示されるようになっています。
ソースファイルの構成
オブジェクト指向エクササイズ(ES2015+版)を適用
仕様の改変内容
基本的には、ドットインストール版と同じ仕様ですが、くじの種類ごとに当たる確率が変化するように「重み」をつけています。
例えば、'中吉'は重み「高」、'大吉'は重み「低」で、大吉の方が出る確率が低くなります。
動作環境
ドットインストール版では、ブラウザ上でサーバなしで動作していましたが、オブジェクト指向版では、サーバ上で動作させる必要があります。
理由は、ES2015+のモジュールを使っており、それを使用する場合はクロスドメイン制約に引っかかってしまうからです。
以下のような環境で動作しました。
Google Chrome 69
node.js http-server
エクササイズ後のソースファイルの構成
OmikujiBox.jsとOmikujiButton.jsをモジュール化して別ファイルとしました。これは、「6. すべてのエンティティを小さくすること」を適用した結果です。
エクササイズ後のソースファイルの内容
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>おみくじ</title>
<link rel="stylesheet" href="css/styles.css">
</html>
<body>
<div id="button">?</div>
<script type="module" src="js/main.js"></script>
</body>
</html>
ES Moduleを使うため、scriptタグにtype="module"を追加しました。
<script src="js/main.js"></script>
<script type="module" src="js/main.js"></script>
css/style.css
何もかわっていないため、割愛します。
js/main.js
import OmikujiButton from './OmikujiButton.js';
import OmikujiBox from './OmikujiBox.js';
const KUJI_WEIGHT = {
LOW: 1,
MIDDLE: 3,
HIGH: 5
}
class KujiName {
constructor(name) {
this.name = name;
}
toString() {
return this.name;
}
}
class Kuji {
constructor(name, weight) {
this.name = name;
this.weight = weight;
}
}
(() => {
'use strict';
const omikujiBox = new OmikujiBox();
const kujis = [
{name: '小吉', weight: KUJI_WEIGHT.MIDDLE},
{name: '中吉', weight: KUJI_WEIGHT.HIGH},
{name: '末吉', weight: KUJI_WEIGHT.MIDDLE},
{name: '大吉', weight: KUJI_WEIGHT.LOW},
{name: '凶', weight: KUJI_WEIGHT.LOW},
].forEach((element) => {
const kujiName = new KujiName(element.name);
const kuji = new Kuji(kujiName, element.weight);
omikujiBox.add(kuji);
})
const button = new OmikujiButton(omikujiBox);
})();
js/OmikujiBox.js
export default class {
constructor() {
this.kujis = [];
}
sumOfWeight() {
let sumOfWeight = 0;
this.kujis.forEach(function(element) {
let kuji = element.kuji;
sumOfWeight += kuji.weight;
});
return sumOfWeight;
}
add(kuji) {
this.kujis.push({"kuji": kuji});
}
draw() {
let randomValue = Math.random();
const sumOfWeight = this.sumOfWeight();
let upperRange = 0;
const hitElement = this.kujis.find(function(element) {
let kuji = element.kuji;
upperRange += (kuji.weight / sumOfWeight);
return randomValue < upperRange;
});
if (hitElement) {
let kuji = hitElement.kuji;
return kuji.name;
}
return "ハズレ";// 基本的にはありえないルート
}
}
js/OmikujiButton.js
export default class {
constructor(omikujiBox) {
this.button = document.getElementById('button');
this.button.addEventListener('click', () => {
const kujiName = omikujiBox.draw();
const kujiNameString = kujiName.toString();
this.button.textContent = kujiNameString;
});
button.addEventListener('mousedown', () => {
this.className = 'pushed';
})
button.addEventListener('mouseup', () => {
this.className = '';
})
}
}
最後に
JavaScriptで、Javaのようなクラスベースのオブジェクト指向が使えるようになってきました。これは、オブジェクト指向を学ぶ敷居が低くなった、ということで、とても歓迎すべきことだと思います。
オブジェクト指向エクササイズは、慣れないと非常に時間がかかります。ドットインストール版のソースからリファクタリングしながら徐々に9つのルールを適用していきましたが、ドットインストール版のソースをゼロから作るのより十倍は時間がかかったと思います。しかし、これも慣れていけば、もっとスピードが上がっていくはずです。運動と同じで繰り返すことで身につけるものですね。だから「エクササイズ」というのか!腹落ちしました。