ES2015+でオブジェクト指向エクササイズ

# はじめに


# オブジェクト指向エクササイズのルール（ES2015+用に一部改変）
「オブジェクト指向エクササイズ」は、９つのルールからなりますが、ES2015+用に一部制約を緩めています。

1. １つのメソッドにつきインデントは１段階までにすること
2. else 句を使用しないこと
3. すべてのプリミティブ型と文字列型をラップすること
4. １行につきドットは１つまでにすること  
**＝＞　（ES2015+用追記）但し、thisの後ろのドットはカウントしない**
5. 名前を省略しないこと
6. すべてのエンティティを小さくすること　[補足: 50行を超えるクラス、10ファイルを超えるパケッージを作らない]
7. １つのクラスにつきインスタンス変数は２つまでにすること
8. ファーストクラスコレクションを使用すること
9. Getter, Setter, プロパティを使用しないこと

# 使用する題材
ドットインストールの[JavaScriptでおみくじを作ろう](https://dotinstall.com/lessons/omikuji_js_v3)のプログラムをベースにします。

## 動作概要
ブラウザ上で、最初以下のような画面が表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73241/452100ff-8223-75c6-eafa-5c90c513ebdf.png)

円内をクリックすると、おみくじの結果が表示されます。
![スクリーンショット 2018-09-21 6.27.45.png](https://qiita-image-store.s3.amazonaws.com/0/73241/b777ebf9-31dd-e698-a16c-f71e87954cb4.png)

おみくじは、乱数を使ってランダムに表示されるようになっています。

## ソースファイルの構成
ソースファイルの構成は、以下の通りシンプルなものです。
![スクリーンショット 2018-09-21 6.30.57.png](https://qiita-image-store.s3.amazonaws.com/0/73241/4e5a944f-e8bc-8d58-648c-dcf425b793b8.png)

# オブジェクト指向エクササイズ(ES2015+版)を適用
## 仕様の改変内容
基本的には、ドットインストール版と同じ仕様ですが、くじの種類ごとに当たる確率が変化するように「重み」をつけています。
例えば、'中吉'は重み「高」、'大吉'は重み「低」で、大吉の方が出る確率が低くなります。

## 動作環境
ドットインストール版では、ブラウザ上でサーバなしで動作していましたが、オブジェクト指向版では、サーバ上で動作させる必要があります。
理由は、ES2015＋のモジュールを使っており、それを使用する場合はクロスドメイン制約に引っかかってしまうからです。
以下のような環境で動作しました。
Google Chrome 69
node.js http-server

## エクササイズ後のソースファイルの構成
![スクリーンショット 2018-09-26 6.23.46.png](https://qiita-image-store.s3.amazonaws.com/0/73241/cb6dc513-dcfc-257f-5738-7e3256e64c04.png)
OmikujiBox.jsとOmikujiButton.jsをモジュール化して別ファイルとしました。これは、「6. すべてのエンティティを小さくすること」を適用した結果です。

<!--
## エクササイズ後のクラス図
以下の３つのクラスを作るようにしました。
@startuml
class OmikujiBox {
  number sumOfWeight()
  void add(Kuji kuji)
  void draw()
}

class KujiName {
  name
}

class Kuji {
  constructor(string name, number weight)
  string name
  number weight
}

class OmikujiButton {
  void click()
  void mousedown()
  void mouseup()
}

OmikujiButton --"1" OmikujiBox
OmikujiBox o--"*" Kuji
Kuji --"1" KujiName
@enduml

-->





## エクササイズ後のソースファイルの内容
```html:index.html
<!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"を追加しました。

```html:変更前
<script src="js/main.js"></script>
```
```html:変更後
<script type="module" src="js/main.js"></script>
```

### css/style.css
何もかわっていないため、割愛します。

### js/main.js


```JavaScript: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

```JavaScript: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

```JavaScript: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のようなクラスベースのオブジェクト指向が使えるようになってきました。これは、オブジェクト指向を学ぶ敷居が低くなった、ということで、とても歓迎すべきことだと思います。

オブジェクト指向エクササイズは、慣れないと非常に時間がかかります。ドットインストール版のソースからリファクタリングしながら徐々に９つのルールを適用していきましたが、ドットインストール版のソースをゼロから作るのより十倍は時間がかかったと思います。しかし、これも慣れていけば、もっとスピードが上がっていくはずです。運動と同じで繰り返すことで身につけるものですね。だから「エクササイズ」というのか！腹落ちしました。