オブジェクト指向の魔法:JavaScriptで電卓アプリを作ろう

JavaScript
Screenshot

こんにちは!

今回は、

読者
読者

「JavaScriptをオブジェクト指向で書く方法を知りたい」

「JavaScriptをオブジェクト指向で書くメリットを知りたい」

「アプリを作りながらJavaScriptのオブジェクト指向を勉強したい」

などお考えの方に向けて、JavaScriptのオブジェクト指向をテーマに書いていきたいと思います。

本記事では、以下のようなことがわかるようになっています。

  • オブジェクト指向とは何か
  • オブジェクト指向のメリット
  • オブジェクト指向を使った電卓アプリの開発

電卓アプリを作りながら学んでいこうと思います。

電卓アプリというと簡単そうに聞こえますが、意外とそうでもありません。

何をオブジェクトとして切り出すか、どのようなメソッドを持たせれば十分か、オブジェクトをどう活用していくかなど考えることはたくさんあります。

電卓アプリを作る過程で、オブジェクト指向の考え方に慣れていきましょう。

それでは、みていきましょう!

前提

本記事を書くにあたって、以下のようなことを前提としています。

  • HTML/CSSの基礎的な知識
  • JavaScriptの概要
  • VSCodeなどのテキストエディタのインストール

オブジェクト指向とは

オブジェクト指向プログラミング(OOP)は、プログラムを作るための方法の1つです。これを理解するためには、まず「オブジェクト」という考え方を理解する必要があります。

オブジェクトは、現実世界のものや概念をプログラム上に表現したものです。例えば、犬や猫、車、果物などがオブジェクトの例です。それぞれのオブジェクトは、特定の性質(属性)と行動(メソッド)を持っています。例えば、犬の属性には「色」や「体重」があり、行動には「吠える」や「走る」などがあります。

オブジェクト指向プログラミングでは、これらのオブジェクトをプログラムで扱います。クラスと呼ばれるものを使って、オブジェクトの設計図を作成します。クラスは、オブジェクトが持つ属性や行動を定義します。例えば、「犬」のクラスには、色や体重といった属性と、吠えると走るといった行動が定義されます。

例えば、「動物」クラスを作ってみます。このクラスには、全ての動物が共通して持つ性質や行動を定義します

class Animal {
  constructor(name, age) {
    this.name = name; // 名前と年齢という属性を持つ
    this.age = age;
  }

  speak() { // 鳴くという行動を持つ
    console.log("音を出す");
  }
}

このクラスを使って、具体的な動物を作ることができます。例えば、「犬」や「猫」などです。これらの動物は、動物クラスを継承しています。つまり、動物クラスの性質や行動を引き継いでいると考えることができます。

class Dog extends Animal {
  constructor(name, age, breed) {
    super(name, age); // 親クラスのコンストラクタを呼び出す
    this.breed = breed; // 犬種という追加の属性を持つ
  }

  bark() { // 吠えるという追加の行動を持つ
    console.log("ワンワン");
  }
}

このように、オブジェクト指向プログラミングでは、現実世界のものをプログラム上に再現し、それらのオブジェクトが持つ性質や行動を定義します。これにより、プログラムの設計が分かりやすくなり、コードを効率的に管理できるようになります。

電卓アプリを作る

目標成果物

以下のような電卓アプリを制作していきます。

ソースコードは以下に置いておきます。

GitHub - tarao96/calculator: javascriptで作成した電卓アプリ
javascriptで作成した電卓アプリ. Contribute to tarao96/calculator development by creating an account on GitHub.

HTMLコーディング

プロジェクトルートにHTMLファイルを作成します。

まずは、雛形を作ります。

VSCodeをお使いの場合は、「!」と入力してEnterキーを押すとHTMLの雛形ができます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Calculator</title>
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

事前に、これから使う予定のCSSとJavaScriptのファイルを読み込んでおきましょう。

bodyタグの中に電卓の要素を入れていきます。

HTMLに関してはそこまで難しくないので、一気に書いてしまいます。

<div class="wrapper">
    <section class="screen">
        0
    </section>

    <section class="calc-buttons">
        <div class="calc-button-row">
            <button class="calc-button double">
                C
            </button>
            <button class="calc-button">

            </button>
            <button class="calc-button">
                ÷
            </button>
        </div>

        <div class="calc-button-row">
            <button class="calc-button">
                7
            </button>
            <button class="calc-button">
                8
            </button>
            <button class="calc-button">
                9
            </button>
            <button class="calc-button">
                ×
            </button>
        </div>

        <div class="calc-button-row">
            <button class="calc-button">
                4
            </button>
            <button class="calc-button">
                5
            </button>
            <button class="calc-button">
                6
            </button>
            <button class="calc-button">

            </button>
        </div>

        <div class="calc-button-row">
            <button class="calc-button">
                1
            </button>
            <button class="calc-button">
                2
            </button>
            <button class="calc-button">
                3
            </button>
            <button class="calc-button">
                +
            </button>
        </div>

        <div class="calc-button-row">
            <button class="calc-button triple">
                0
            </button>
            <button class="calc-button">
                =
            </button>
        </div>
    </section>
</div>

なんとなくわかるかと思いますが、screenクラスが計算結果を表示するところで、calc-buttonとなっているところは電卓の数字や記号が入るクラスです。

さて、これでHTMLは用意できたので、次にCSSを書いていきましょう。

CSSコーディング

次に、プロジェクトルートに「style.css」という名前でCSSファイルを作成します。

まずは初期設定を行います。

html {
    box-sizing: border-box;
    height: 100%;
}
*,
*::before,
*::after{
    box-sizing: inherit;
    margin: 0;
    padding: 0;
}

box-sizing: border-box;はpaddingとborderをwidthやheightの領域内に含めるという意味です。

図にすると以下のようなイメージです。

次に、body要素全体のCSSを書いていきます。

body {
    align-items: center;
    background: linear-gradient(320deg, orange, skyblue, limegreen);
    display: flex;
    font-family: 'Dosis', sans-serif;
    font-display: swap;
    height: inherit;
    justify-content: center;
}

ここでは、電卓を左右中央寄せにしたり、フォントを設定したりなどしています。

次に、電卓全体を覆うラッパーのCSSを書いていきます。

.wrapper {
    border-radius: 16px;
    box-shadow: 0 4px 30px rgba(35, 35, 35, 0.1);
    color: #232323;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    background: rgba(255, 255, 255, 0.30);
    border: 1px solid rgba(255, 255, 255, 0.34);
    flex-basis: 400px;
    height: 540px;
    padding: 20px 35px;
}

backdrop-filterプロパティが見慣れないと思いますので、簡単にご紹介します。

backdrop-filter プロパティを使うと、背景にさまざまな効果を与えることができます。

今回の場合は、backdrop-filter: blur(4px);で電卓の背景にぼかしを入れています。

backdrop-filterプロパティは一部のブラウザで動作しないことがあるため、-webkit-backdrop-filterで対応しています。

.screen {
    backdrop-filter: blur(5.5px);
    -webkit-backdrop-filter: blur(5.5px);
    background: rgba(255, 255, 255, 0.75);
    border: 1px solid rgba(255, 255, 255, 0.01);
    border-radius: 16px;
    box-shadow: 0 4px 30px rgba(35, 35, 35, 0.1);
    color: #232323;
    font-size: 35px;
    overflow: auto;
    padding: 20px;
    text-align: right;
    width: 326px;
}

次に、スクリーンのCSSです。スクリーンは先ほど述べたように、計算結果の表示部分です。

こちらは電卓よりも深いぼかしを入れています。

次は数字や記号類のボタンを一気に見ていきます。

.calc-button-row {
    display: flex;
    justify-content: space-between;
    margin: 5% 0;
}
.calc-button{
    backdrop-filter: blur(5.5px);
    -webkit-backdrop-filter: blur(5.5px);
    background: rgba(255, 255, 255, 0.75);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    box-shadow: 0 4px 30px rgba(35, 35, 35, 0.1);
    color: #232323;
    flex-basis: 20%;
    font-family: inherit;
    font-size: 24px;
    height: 65px;
}
.calc-button:last-child{
    backdrop-filter: blur(5.5px);
    -webkit-backdrop-filter: blur(5.5px);
    background: rgba(255, 255, 255, 0.75);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    box-shadow: 0 4px 30px rgba(35, 35, 35, 0.1);
    color: #fff;
    background: #d72880;
}
.calc-button:last-child:hover{
    background-color: inherit;
    color: inherit;
}
.calc-button:hover{
    background-color: inherit;
}
.calc-button:active{
    background-color: #ffef78;
}
.double{
    flex-basis: 47%;
}
.triple{
    flex-basis: 73%;
}

レイアウトはflexboxで整え、ホバーやアクティブ時の色を変えたりなど行なっています。

こちらでCSSは完了です。

JavaScriptコーディング

お待たせしました。ここからが本記事の主題です。

計算ロジックをオブジェクト指向的に実装していきましょう。

クラス設計

まずは設計を行います。

電卓計算ロジックを実装するにはどのようなクラスが必要になるでしょうか。

おそらく以下で十分でしょう。

  • 数字クラス
  • 記号クラス

数字クラスには計算される数字をデータに持ち、四則演算のメソッドなどがあれば良いと思います。

また、記号クラスには記号のデータとそのデータを操作するメソッドがあれば良いと思います。

もう少し詳しく見ていきましょう。

実際に電卓を操作するときの動きとしては、例えば以下のようになります。

初期表示(スクリーン: 0)

数字を入力(スクリーン: 1回目に入力された数字)

「+」を入力(スクリーン: 1回目に入力された数字)

数字を入力(スクリーン: 2回目に入力された数字)

「=」を入力(スクリーン: 2回目に入力された数字)

合計値出力(スクリーン: 1回目と2回目を足し算した数字)

この操作を実現するには、どのようなデータの持ち方をすれば良いかを考えてみます。

上の図から、以下ような設計にします。

  • 数字クラスには「新しく入力された数字」と「それ以前の計算結果」を持っておく
  • 数字クラスは計算結果をクラス内部のデータとして持っておき、「=」が押された時に計算結果をスクリーンに出力する

こちらの実装方針をもとにクラス図を書いてみます。

Numberクラスのnumberプロパティは新しく入力された数字データで、previousNumberはこれまでの計算結果です。

numberプロパティは「←」入力時に桁を削除する機能があることと、DOMからスクリーンの数字を取得する際に文字列として取得されるため、データ型は文字列にしておきます。

メソッドは四則演算やリセットなどです。

Numberクラス

それでは、Numberクラスから実装していきましょう。

class Number {
    constructor(number = '0', previousNumber = null) {
        this.previousNumber = previousNumber
        this.number = number;
    }

    add() {
        let number = parseInt(this.number);
        let previousNumber = parseInt(this.previousNumber);
        let total = previousNumber + number;
        this.number = String(total);
    }

    sub() {
        let number = parseInt(this.number);
        let previousNumber = parseInt(this.previousNumber);
        let total = previousNumber - number;
        this.number = String(total);
    }

    multiply() {
        let number = parseInt(this.number);
        let previousNumber = parseInt(this.previousNumber);
        let total = previousNumber * number;
        this.number = String(total);
    }

    divide() {
        let number = parseInt(this.number);
        let previousNumber = parseInt(this.previousNumber);
        let total = previousNumber / number;
        this.number = String(total);
    }

    reset() {
        this.number = '0';
        this.setScreen();
    }

    addstring(number) {
        this.number += number;
        this.setScreen();
    }

    substring() {
        if (this.number.length === 1) {
            this.number = 0;
        } else {
            this.number = this.number.slice(0, -1);
        }
        this.setScreen();
    }

    set(number) {
        this.number = number;
        this.setScreen();
    }

    setScreen() {
        document.querySelector('.screen').innerText = this.number;
    }

    get() {
        return this.number;
    }

    setPreviousNumber() {
        this.previousNumber = this.number; // 入れ替える
        this.number = '0';
    }

    calc(symb) {
        if (!symb) return;
        switch(symb) {
            case '+':
                this.add();
                break;
            case '':
                this.sub();
                break;
            case '×':
                this.multiply();
                break;
            case '÷':
                this.divide();
                break;
        }
    }
}

Numberクラスが持つプロパティは直接操作せず、メソッドから取得や変更ができるようにしておきます。

set(number) {
    this.number = number;
    this.setScreen();
}
    
get() {
    return this.number;
}

こういったプロパティをセット・取得するメソッドのことをそれぞれ「セッター」と「ゲッター」と言います。

なぜセッターやゲッターを使ってプロパティを操作するかというと、プロパティを安全に操作するためです。

セッターやゲッターを使わないと、他のクラスからも簡単にプロパティを操作できてしまいますが、セッターやゲッターがあるとそのクラスのメソッドからしか操作することができなくなります。結果、プロパティを安全に扱うことができるようになります。

calcメソッドは「=」が押された時に最終的な計算を行うメソッドです。入力された記号によって処理を分けています。

calc(symb) {
    if (!symb) return;
    switch(symb) {
        case '+':
            this.add();
            break;
        case '':
            this.sub();
            break;
        case '×':
            this.multiply();
            break;
        case '÷':
            this.divide();
            break;
    }
}

Symbクラス

次に、Symbクラスを実装していきます。

本当はSymbolクラスとしたかったのですが、Symbolという名称はすでにJavaScriptの標準的なクラスとして存在してしまっているので、規約違反になってしまいます。

そこで、重複を避けるためにSymbクラスとしておきました。

ちなみに、標準で定義されているクラスやメソッドなどを予約語と言います。頭の片隅に入れておきましょう。

class Symb {
    constructor(symbol) {
        this.symbol = symbol;
    }

    reset() {
        this.symbol = null;
    }

    set(symbol) {
        this.symbol = symbol;
    }

    get() {
        return this.symbol;
    }   
}

Symbクラスは非常にシンプルですね。

セッター・ゲッターとリセットのみです。

イベントの処理

それでは、事前に準備しておいたクラスを使って、ボタンが押された時の挙動を書いていきましょう。

const screen = document.querySelector('.screen');
const calcButtons = document.querySelector('.calc-buttons');
let number = new Number();
let symb = new Symb();

function buttonClick(value) {
    if(isNaN(value)){
        handleSymbol(value);
    } else {
        handleNumber(value);
    }
}

function handleSymbol(symbol) {
    switch(symbol) {
        case 'C':
            number.reset();
            symb.reset();
            break;
        case '=':
            number.calc(symb.get());
            number.setScreen();
            symb.reset();
            break;
        case '':
            number.substring();
            break;
        case '+':
        case '':
        case '×':
        case '÷':
            number.setPreviousNumber();
    }
    symb.set(symbol);
}

function handleNumber(value) {
    if (number.get() == '0') {
        number.set(value);
    } else {
        number.addstring(value);
    }
}

calcButtons.addEventListener('click', function(event) {
    buttonClick(event.target.innerText);
});

まず、スクリーン要素と各ボタン要素を取得しておきます。

次に、押されたボタンが数字なのか記号なのかを判別し、それぞれの処理に入れます。

記号が押された場合は、それぞれの記号によって計算処理が走ります。数字が押された時は、スクリーンの数字を操作します。

事前にクラスとして定義していたことで、コードが随分とスッキリしていますね!

この可読性の高さこそがオブジェクト指向の大きな強みです。

動作確認

まとめ

いかがでしたでしょうか。

本記事に見てきたように、オブジェクト指向を使うことで様々な恩恵が得られます。

  1. コードの再利用性
  2. 保守性
  3. 拡張性
  4. 可読性

などなど。

この電卓アプリの機能をさらに拡張したいと思った場合でも、クラスを定義してあるので容易に機能を拡張していくことができるでしょう。

また、クラスを作ることでコードを再利用することができるようになりました。オブジェクト指向プログラミングでは、DRY原則が自然と守られるようにもなります。

ただ、筆者のイチオシポイントはやはりコードの可読性ですね。オブジェクトとして扱うことで、かなりコードが理解しやすくなり、結果的に保守性も高まります。

本記事をきっかけにオブジェクト指向に慣れ親しんでいただけたら嬉しいです。

ではでは😊

コメント

タイトルとURLをコピーしました