JavaScript | 1 日 120 分 × 7 日アプリ学習:クラス設計アプリ(オブジェクト指向)

Web APP JavaScript
スポンサーリンク

1日目のゴールと作るもののイメージ

1日目のテーマは
「class を使って、“それっぽいオブジェクト”を自分で設計できるようになること」です。

キーワードはこの3つです。

class
カプセル化(中身を隠す・守る)
責務分離(役割を分ける)

いきなり難しいアプリにはしません。
まずは「シンプルなメモ」を扱うクラスを作りながら、
オブジェクト指向の“感覚”をつかんでいきます。


class とは何かを直感でつかむ

「設計図」と「実物」というイメージ

JavaScript の class は、ざっくり言うと

「オブジェクトを作るための設計図」

です。

設計図(class)から、
実物(インスタンス)を何個でも作れます。

まずは、超シンプルな例から。

class Memo {
  constructor(text) {
    this.text = text;
  }
}

const m1 = new Memo("買い物に行く");
const m2 = new Memo("勉強する");

console.log(m1.text); // 買い物に行く
console.log(m2.text); // 勉強する
JavaScript

ここでやっていることを言葉で整理すると、

Memo という「メモ用の設計図」を定義する。
constructor で「作るときに必要な情報」(text)を受け取る。
new Memo(…) で「実物のメモ」を作る。

この「設計図 → 実物」の感覚が、class の出発点です。


constructor と this の役割をちゃんと理解する

constructor は「生まれた瞬間に呼ばれる関数」

constructor は、そのクラスから new されたときに
自動で呼ばれる特別なメソッドです。

class Memo {
  constructor(text) {
    console.log("Memo が 1 つ生まれました");
    this.text = text;
  }
}
JavaScript

new Memo("テスト") とすると、

「Memo が 1 つ生まれました」
というログが出て、this.text"テスト" が入ります。

this は「今まさに作っている(または扱っている)その1個」

this は、「今操作しているそのインスタンス自身」を指します。

class Memo {
  constructor(text) {
    this.text = text;
  }

  show() {
    console.log("メモ:", this.text);
  }
}

const m = new Memo("ご飯を作る");
m.show(); // メモ: ご飯を作る
JavaScript

ここでの this.text は、

constructor の中でも
show メソッドの中でも

「同じメモオブジェクトの text」を指しています。

重要ポイント:

this は「クラス全体」ではなく、
「その1個、その瞬間のインスタンス」を指す。

これが分かると、
「同じ設計図から作ったのに、中身が違うオブジェクト」を
自然に扱えるようになります。


カプセル化とは何か(“中身を守る”という発想)

なんでも外から触れると、すぐ壊れる

さっきの Memo クラスは、こうでした。

class Memo {
  constructor(text) {
    this.text = text;
  }
}
JavaScript

これだと、外からこう書けてしまいます。

const m = new Memo("大事なメモ");
m.text = 12345; // 文字列じゃないものを入れてしまえる
JavaScript

「text は文字列であってほしい」のに、
誰でも好き勝手に書き換えられる状態です。

これだと、アプリが大きくなったときに
「どこでおかしくなったのか」が追いづらくなります。

そこで出てくるのが「カプセル化」です。

カプセル化=「中身を直接触らせず、窓口を用意する」

JavaScript では、# を使って「プライベートフィールド」を作れます。

class Memo {
  #text; // 外から直接触れない

  constructor(text) {
    this.#text = text;
  }

  getText() {
    return this.#text;
  }

  setText(newText) {
    if (typeof newText !== "string") {
      console.warn("文字列以外はセットできません");
      return;
    }
    this.#text = newText;
  }
}

const m = new Memo("最初のメモ");
console.log(m.getText()); // 最初のメモ

m.setText("書き換えたメモ");
console.log(m.getText()); // 書き換えたメモ

m.setText(123); // 警告が出て、値は変わらない
JavaScript

ここで起きていることを整理すると、

#text はクラスの外からは見えない・触れない。
代わりに、getText / setText という「窓口メソッド」を用意する。
setText の中で「文字列かどうか」をチェックしている。

つまり、

「中身を守るために、直接触らせず、ルール付きの入り口を作る」

これがカプセル化です。


責務分離とは何か(“1人に何役もやらせない”)

なんでもかんでも 1 クラスに詰め込むと地獄になる

例えば、こんなクラスを想像してみてください。

メモの内容を持つ
メモの一覧を管理する
メモを検索する
メモを削除する
画面に表示する

これを全部 1 クラスに詰め込むと、
そのクラスは「なんでも屋」になってしまいます。

変更に弱く、テストしづらく、
読んでいても「結局このクラス、何者?」となりがちです。

そこで出てくるのが「責務分離」です。

責務分離=「役割ごとにクラスを分ける」

例えば、こう分けられます。

Memo:1つのメモそのものを表すクラス
MemoList:複数のメモを管理するクラス

まずは Memo をシンプルに保ちます。

class Memo {
  #text;

  constructor(text) {
    this.#text = text;
  }

  getText() {
    return this.#text;
  }

  setText(newText) {
    if (typeof newText !== "string") {
      console.warn("文字列以外はセットできません");
      return;
    }
    this.#text = newText;
  }
}
JavaScript

次に、MemoList を作ります。

class MemoList {
  #memos;

  constructor() {
    this.#memos = [];
  }

  add(text) {
    const memo = new Memo(text);
    this.#memos.push(memo);
  }

  getAll() {
    return this.#memos;
  }

  findByKeyword(keyword) {
    return this.#memos.filter(memo => memo.getText().includes(keyword));
  }
}
JavaScript

ここでの役割分担はこうです。

Memo:1つのメモの中身を守る・扱う。
MemoList:メモの集まりを管理する(追加・検索など)。

重要ポイント:

「1クラス=1つの責任」に近づけるほど、
コードは読みやすく、変更しやすくなります。


小さな例題:コンソールで動く簡単なメモ管理

クラスを使わない場合との違いを感じる

クラスを使わないと、こうなりがちです。

const memos = [];

function addMemo(text) {
  memos.push(text);
}

function showMemos() {
  memos.forEach((m, i) => {
    console.log(i + 1, m);
  });
}
JavaScript

これはこれで動きますが、

メモに「作成日時」や「重要フラグ」などを足したくなったとき、
配列の中身がどんどん複雑になっていきます。

クラスを使うと、こう書けます。

class Memo {
  #text;
  #createdAt;

  constructor(text) {
    this.#text = text;
    this.#createdAt = new Date();
  }

  getText() {
    return this.#text;
  }

  getCreatedAt() {
    return this.#createdAt;
  }
}

class MemoList {
  #memos;

  constructor() {
    this.#memos = [];
  }

  add(text) {
    const memo = new Memo(text);
    this.#memos.push(memo);
  }

  showAll() {
    this.#memos.forEach((memo, index) => {
      console.log(
        index + 1,
        memo.getText(),
        "(" + memo.getCreatedAt().toLocaleString() + ")"
      );
    });
  }
}

const list = new MemoList();
list.add("牛乳を買う");
list.add("本を読む");
list.showAll();
JavaScript

ここで感じてほしいのは、

「メモに何か属性を足したくなったとき、Memo クラスだけを見ればいい」

という安心感です。

配列の中身が「ただの文字列」ではなく、
「ちゃんとしたメモオブジェクト」になっていることで、
設計がスッキリしていきます。


1日目でいちばん深く理解してほしいこと

今日のキーワードを、感覚レベルでまとめます。

class:
「オブジェクトの設計図」。
constructor で「生まれた瞬間の初期化」を書く。
this は「その1個の実物」を指す。

カプセル化:
「中身を直接触らせず、ルール付きの窓口を用意する」。
JavaScript では #フィールド と getter / setter で実現できる。

責務分離:
「1クラスに何役もやらせず、役割ごとにクラスを分ける」。
Memo は「1つのメモ」、MemoList は「メモの集まり」。

1日目は、
「クラスってそういう“考え方の箱”なんだな」
という感覚が持てていれば十分です。

次のステップでは、この Memo / MemoList をベースにして、

「メソッドを増やす」
「状態を変える」
「画面(DOM)とクラスをどうつなぐか」

といったところに進んでいきます。
そのとき、今日の「カプセル化」と「責務分離」が、
かなり効いてきます。

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