- Day 26:Classで「銀行口座」を設計してみる
- Classとは何かをざっくりイメージする
- Step 1:銀行口座クラスの骨組みを作る
- Step 2:Propertyとアクセス修飾子(public / private / protected / readonly)
- Step 3:deposit() を実装する(入金)
- Step 4:withdraw() を実装する(出金)
- Step 5:getBalance() を実装する(残高確認)
- Step 6:銀行口座クラスの完成形と使用例
- Day 26のまとめ
- Day 26:4つのClassで「オブジェクト指向の感覚」をつかむ
- Classの基本をもう一度整理する
- User Class:ユーザー情報をクラスで表現する
- Product Class:商品情報をクラスで表現する
- Cart Class:商品をまとめて管理するクラス
- BankAccount Class:お金を扱うクラスで「安全性」を意識する
- 4つのClassをまとめた練習テンプレート
- Day 26演習のまとめ
Day 26:Classで「銀行口座」を設計してみる
第4週のDay 26では、いよいよ Class(クラス) に入っていきます。 オブジェクト指向の入り口として、とても定番で分かりやすい題材を使います。
銀行口座クラスを作り、
deposit()/withdraw()/getBalance()を実装する。
扱うキーワードは次のとおりです。
- Class
- Constructor
- Property
- Method
publicprivateprotectedreadonly
「銀行口座」という身近なイメージを使いながら、 クラスの基本をステップバイステップでかみ砕いていきます。
Classとは何かをざっくりイメージする
「設計図」と「実体」の関係
クラスは、よく「設計図」にたとえられます。
- クラス:銀行口座の設計図
- インスタンス:実際の銀行口座(1つ1つの口座)
TypeScriptのクラスは、次のような要素で構成されます。
- Property(プロパティ):クラスが持つ「状態」
- Method(メソッド):クラスが持つ「振る舞い(処理)」
- Constructor(コンストラクタ):インスタンスを作るときに呼ばれる「初期化処理」
これを銀行口座に当てはめると、
- Property:口座番号、名義、残高
- Method:入金(deposit)、出金(withdraw)、残高確認(getBalance)
- Constructor:口座を作るときに初期残高や名義を設定する
というイメージになります。
Step 1:銀行口座クラスの骨組みを作る
まずは、最低限の骨組みから始めます。
// 銀行口座クラスの基本形
class BankAccount {
// プロパティ(状態)
private balance: number; // 残高は外から直接触らせたくないのでprivateにする
// コンストラクタ(初期化)
constructor(initialBalance: number) {
this.balance = initialBalance;
}
// メソッド(振る舞い)
deposit(amount: number): void {
// 入金処理
}
withdraw(amount: number): void {
// 出金処理
}
getBalance(): number {
// 残高を返す
return this.balance;
}
}
TypeScriptここではまだ中身を空にしていますが、 クラスの構造としてはすでに「銀行口座っぽい形」になっています。
Step 2:Propertyとアクセス修飾子(public / private / protected / readonly)
Propertyとは
プロパティは、クラスが持つ「データ(状態)」です。
銀行口座なら、たとえば次のようなものがあります。
- 口座番号
- 名義
- 残高
アクセス修飾子の役割
TypeScriptのクラスでは、プロパティやメソッドに 次のようなアクセス修飾子を付けることができます。
public- どこからでもアクセス可能(デフォルト)
private- クラスの内部からしかアクセスできない
protected- クラス自身と、そのサブクラスからアクセスできる
readonly- 一度代入したら変更できない(読み取り専用)
銀行口座の「残高」は、外から勝手に書き換えられると困ります。 そのため、private にしてクラスの外から直接触れないようにします。
また、「口座番号」は一度決まったら変わらないものなので、 readonly にするのが自然です。
銀行口座クラスにプロパティを追加する
class BankAccount {
// 口座番号(読み取り専用)
public readonly accountNumber: string;
// 名義(外から参照はできるが、変更はメソッド経由にしたい場合はprivateでもよい)
public ownerName: string;
// 残高(外から直接変更されたくないのでprivate)
private balance: number;
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
// ここにメソッドを追加していく
}
TypeScript重要ポイント:
readonly accountNumber- コンストラクタで一度値を設定したら、その後は変更できません。
private balance- クラスの外から
account.balance = 0のような操作はできなくなります。 - 残高の変更は必ず
deposit/withdrawを通じて行われるようになります。
- クラスの外から
Step 3:deposit() を実装する(入金)
入金メソッド deposit() は、 「指定された金額を残高に足す」処理です。
ただし、現実的には「マイナスの金額を入金しようとしたらエラー」などのチェックも必要です。
class BankAccount {
public readonly accountNumber: string;
public ownerName: string;
private balance: number;
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
// 入金メソッド
public deposit(amount: number): void {
if (amount <= 0) {
// 0以下の金額は入金として受け付けない
throw new Error("入金額は正の数である必要があります");
}
this.balance += amount;
}
// 出金メソッド(後で実装)
public withdraw(amount: number): void {
// ...
}
// 残高取得メソッド
public getBalance(): number {
return this.balance;
}
}
TypeScriptポイント:
public deposit(amount: number): void- メソッドもアクセス修飾子を付けられます。
- ここでは外から呼び出せるように
publicにしています。
this.balance += amount;balanceはprivateですが、クラスの内部からはアクセスできます。- 外からは
account.balanceと書けないので、 「残高の変更は必ずこのメソッドを通る」というルールを守れます。
Step 4:withdraw() を実装する(出金)
出金メソッド withdraw() は、 「指定された金額を残高から引く」処理です。
ここでも、現実的なチェックが必要です。
- マイナスの金額は出金として受け付けない
- 残高より多い金額は引き出せない
class BankAccount {
public readonly accountNumber: string;
public ownerName: string;
private balance: number;
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
public deposit(amount: number): void {
if (amount <= 0) {
throw new Error("入金額は正の数である必要があります");
}
this.balance += amount;
}
// 出金メソッド
public withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("出金額は正の数である必要があります");
}
if (amount > this.balance) {
throw new Error("残高不足です");
}
this.balance -= amount;
}
public getBalance(): number {
return this.balance;
}
}
TypeScript重要ポイント:
- 「残高不足」をチェックしているところが、 クラスの中に「銀行口座のルール」を閉じ込めている部分です。
- 外からは
balanceに直接触れないので、 「ルールを無視して残高をマイナスにする」ことができません。
Step 5:getBalance() を実装する(残高確認)
残高確認メソッド getBalance() は、 「現在の残高を返すだけ」のシンプルなメソッドです。
public getBalance(): number {
return this.balance;
}
TypeScriptこれにより、外からは次のように使えます。
const account = new BankAccount("123-456", "Taro", 1000);
console.log("現在の残高:", account.getBalance()); // 1000
account.deposit(500);
console.log("入金後の残高:", account.getBalance()); // 1500
account.withdraw(300);
console.log("出金後の残高:", account.getBalance()); // 1200
TypeScriptポイント:
- 残高は
privateですが、getBalance()を通じて「読み取りだけ」外に公開しています。 - 「書き込みはメソッド経由」「読み取りは専用メソッド経由」という形にすることで、 クラスの外からの操作をきれいに制御できます。
Step 6:銀行口座クラスの完成形と使用例
ここまでの内容をひとつにまとめた、 練習用テンプレートを載せます。
// day26-bank-account.ts
// Day 26:Classで銀行口座を作る
class BankAccount {
// 口座番号(読み取り専用)
public readonly accountNumber: string;
// 名義(今回はpublicにしておく)
public ownerName: string;
// 残高(外から直接変更されたくないのでprivate)
private balance: number;
// コンストラクタ:口座番号・名義・初期残高を受け取る
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
// 入金メソッド
public deposit(amount: number): void {
if (amount <= 0) {
throw new Error("入金額は正の数である必要があります");
}
this.balance += amount;
}
// 出金メソッド
public withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("出金額は正の数である必要があります");
}
if (amount > this.balance) {
throw new Error("残高不足です");
}
this.balance -= amount;
}
// 残高取得メソッド
public getBalance(): number {
return this.balance;
}
}
// ===== 使用例 =====
const account = new BankAccount("123-456", "Taro", 1000);
console.log("口座番号:", account.accountNumber); // 123-456
console.log("名義:", account.ownerName); // Taro
console.log("初期残高:", account.getBalance()); // 1000
account.deposit(500);
console.log("500円入金後の残高:", account.getBalance()); // 1500
account.withdraw(300);
console.log("300円出金後の残高:", account.getBalance()); // 1200
// 残高不足の例
try {
account.withdraw(5000);
} catch (e) {
console.error("エラー:", (e as Error).message); // 残高不足です
}
// 直接残高をいじろうとするとどうなるか?
// account.balance = 0; // コンパイルエラー:privateなのでアクセス不可
TypeScriptDay 26のまとめ
Day 26では、
- Class(クラス)を「設計図」として捉えること
- Constructor / Property / Method の役割
public/private/readonlyを使って、 「どこから何を触れるか」を型で制御する考え方- 銀行口座クラス
BankAccountを題材に、deposit()/withdraw()/getBalance()を実装する流れ
を、具体的なコードとコメント付きで見てきました。
クラスは、「データ」と「そのデータに対するルール」を ひとつのまとまりとして表現するための仕組みです。
今日の銀行口座クラスをベースに、
- 「ログ出力用のメソッドを追加する」
- 「出金時に手数料を引く」
- 「protectedを使って、サブクラスで振る舞いを拡張する」
といった発展も、少しずつ試してみてください。 クラスの世界が、ぐっと立体的に見えてくるはずです。
Day 26:4つのClassで「オブジェクト指向の感覚」をつかむ
Day 26の演習では、Classの考え方をぐっと身近にするために、 次の4つのクラスを題材にしていきます。
- User Class
- Product Class
- Cart Class
- BankAccount Class
キーワードは変わらずこちらです。
- Class
- Constructor
- Property
- Method
publicprivateprotectedreadonly
「ユーザー」「商品」「カート」「銀行口座」という、 WebアプリやECサイトでよく登場する世界観を使いながら、 クラス設計の基本をステップバイステップでかみ砕いていきます。
Classの基本をもう一度整理する
Classは「設計図」、インスタンスは「実体」
クラスはよく「設計図」にたとえられます。
- Class:UserやProductの「型」と振る舞いの設計図
- インスタンス:実際のユーザー1人、商品1つ、カート1つ
クラスの中には、主に次の要素があります。
- Property(プロパティ):クラスが持つ「状態」
- Method(メソッド):クラスが持つ「振る舞い(処理)」
- Constructor(コンストラクタ):インスタンス生成時の「初期化処理」
さらに、アクセス修飾子で「どこから触れるか」を制御します。
public:どこからでもアクセス可能private:クラスの内部からのみアクセス可能protected:クラス自身とサブクラスからアクセス可能readonly:一度代入したら変更不可
これらをうまく組み合わせることで、 「安全で意図が伝わるクラス設計」ができるようになります。
User Class:ユーザー情報をクラスで表現する
どんなプロパティとメソッドが必要か考える
Userクラスには、たとえば次のような情報と振る舞いが考えられます。
- プロパティ
id(ユーザーID)name(名前)email(メールアドレス)
- メソッド
changeEmail()(メールアドレス変更)greet()(挨拶メッセージを返す)
id は一度決まったら変わらないことが多いので readonly にするのが自然です。
User Classの実装例
// Userクラス:ユーザー情報を表現する
class User {
// ユーザーID(読み取り専用)
public readonly id: number;
// 名前(外から参照・変更可能)
public name: string;
// メールアドレス(外から参照可能だが、変更はメソッド経由にしたい場合はprivateでもよい)
private email: string;
// コンストラクタ:Userインスタンスを初期化する
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
// メールアドレスを変更するメソッド
public changeEmail(newEmail: string): void {
// ここで形式チェックなどを入れることもできます
this.email = newEmail;
}
// 現在のメールアドレスを取得するメソッド
public getEmail(): string {
return this.email;
}
// 挨拶メッセージを返すメソッド
public greet(): string {
return `こんにちは、${this.name}さん!`;
}
}
// 使用例
const user = new User(1, "Taro", "[email protected]");
console.log(user.greet()); // こんにちは、Taroさん!
console.log(user.getEmail()); // [email protected]
user.changeEmail("[email protected]");
console.log(user.getEmail()); // [email protected]
TypeScriptポイント:
readonly idにすることで、「IDを勝手に書き換えられない」安全性を確保しています。emailをprivateにして、変更はchangeEmail()経由にすることで、 「メールアドレス変更のルール」をクラスの中に閉じ込められます。
Product Class:商品情報をクラスで表現する
商品に必要な情報と振る舞いを考える
Productクラスには、次のようなものが考えられます。
- プロパティ
id(商品ID)name(商品名)price(価格)
- メソッド
getPriceWithTax()(税込価格を計算する)
Product Classの実装例
// Productクラス:商品情報を表現する
class Product {
public readonly id: number;
public name: string;
public price: number;
constructor(id: number, name: string, price: number) {
this.id = id;
this.name = name;
this.price = price;
}
// 税込価格を計算するメソッド(例として10%の消費税)
public getPriceWithTax(taxRate: number = 0.1): number {
return Math.floor(this.price * (1 + taxRate));
}
}
// 使用例
const book = new Product(100, "TypeScript入門", 2000);
console.log("商品名:", book.name); // TypeScript入門
console.log("税抜価格:", book.price); // 2000
console.log("税込価格:", book.getPriceWithTax()); // 2200(10%想定)
TypeScriptポイント:
getPriceWithTax()のようなメソッドにロジックを閉じ込めることで、 「税込価格の計算方法」を一箇所に集約できます。- 価格の計算方法が変わっても、このメソッドを直せばよく、 呼び出し側のコードはそのまま使い続けられます。
Cart Class:商品をまとめて管理するクラス
カートに必要なものを整理する
Cartクラスは、「複数の商品をまとめて管理する」役割を持ちます。
- プロパティ
items(カート内の商品一覧)
- メソッド
addProduct()(商品を追加する)removeProduct()(商品を削除する)getTotal()(合計金額を計算する)
ここではシンプルに、「Productの配列」を内部に持つ形で実装してみます。
Cart Classの実装例
// Cartクラス:商品をまとめて管理する
class Cart {
// カート内の商品一覧(外から直接いじられたくないのでprivate)
private items: Product[] = [];
// 商品を追加するメソッド
public addProduct(product: Product): void {
this.items.push(product);
}
// 商品を削除するメソッド(idで削除)
public removeProduct(productId: number): void {
this.items = this.items.filter((item) => item.id !== productId);
}
// カート内の商品一覧を取得するメソッド
public getItems(): Product[] {
// 外から直接配列をいじられないようにコピーを返す
return [...this.items];
}
// 合計金額を計算するメソッド(税抜)
public getTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
// 合計金額(税込)を計算するメソッド
public getTotalWithTax(taxRate: number = 0.1): number {
return this.items.reduce(
(sum, item) => sum + item.getPriceWithTax(taxRate),
0
);
}
}
// 使用例
const cart = new Cart();
const book = new Product(100, "TypeScript入門", 2000);
const pen = new Product(101, "ペン", 200);
cart.addProduct(book);
cart.addProduct(pen);
console.log("カート内の商品:", cart.getItems().map((p) => p.name)); // ["TypeScript入門", "ペン"]
console.log("合計金額(税抜):", cart.getTotal()); // 2200
console.log("合計金額(税込):", cart.getTotalWithTax()); // 2420(10%想定)
cart.removeProduct(101);
console.log("削除後の合計金額(税抜):", cart.getTotal()); // 2000
TypeScript重要ポイント:
itemsをprivateにすることで、 「カートの中身の変更は必ずメソッド経由」というルールを守れます。getItems()でコピーを返しているのは、 外からitemsを直接書き換えられないようにするためです。
BankAccount Class:お金を扱うクラスで「安全性」を意識する
銀行口座に必要なものを整理する
BankAccountクラスは、Day 26の本編でも扱った題材です。 ここでは、他のクラスとのつながりを意識しながら、もう一度整理してみます。
- プロパティ
accountNumber(口座番号)ownerName(名義)balance(残高)
- メソッド
deposit()(入金)withdraw()(出金)getBalance()(残高確認)
BankAccount Classの実装例
// BankAccountクラス:銀行口座を表現する
class BankAccount {
// 口座番号(読み取り専用)
public readonly accountNumber: string;
// 名義
public ownerName: string;
// 残高(外から直接変更されたくないのでprivate)
private balance: number;
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
// 入金メソッド
public deposit(amount: number): void {
if (amount <= 0) {
throw new Error("入金額は正の数である必要があります");
}
this.balance += amount;
}
// 出金メソッド
public withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("出金額は正の数である必要があります");
}
if (amount > this.balance) {
throw new Error("残高不足です");
}
this.balance -= amount;
}
// 残高取得メソッド
public getBalance(): number {
return this.balance;
}
}
// 使用例
const account = new BankAccount("123-456", "Taro", 1000);
console.log("口座番号:", account.accountNumber); // 123-456
console.log("名義:", account.ownerName); // Taro
console.log("初期残高:", account.getBalance()); // 1000
account.deposit(500);
console.log("500円入金後の残高:", account.getBalance()); // 1500
account.withdraw(300);
console.log("300円出金後の残高:", account.getBalance()); // 1200
try {
account.withdraw(5000);
} catch (e) {
console.error("エラー:", (e as Error).message); // 残高不足です
}
// account.balance = 0; // コンパイルエラー:privateなのでアクセス不可
TypeScriptポイント:
- お金を扱うクラスでは、
privateやreadonlyを使って 「勝手に書き換えられない」「ルールを必ず通る」設計にすることがとても重要です。 - こうした「安全性の意識」は、セキュリティや信頼性にも直結します。
4つのClassをまとめた練習テンプレート
最後に、今回の4つのクラスをひとつのファイルにまとめた 練習用テンプレートを載せます。 このままコピーして、プロパティやメソッドを増やしたり、 protected を使った継承を試したりしながら、 クラス設計の感覚を育ててみてください。
// day26-classes-practice.ts
// Day 26:User / Product / Cart / BankAccount クラス演習
// ===== User Class =====
class User {
public readonly id: number;
public name: string;
private email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
public changeEmail(newEmail: string): void {
this.email = newEmail;
}
public getEmail(): string {
return this.email;
}
public greet(): string {
return `こんにちは、${this.name}さん!`;
}
}
// ===== Product Class =====
class Product {
public readonly id: number;
public name: string;
public price: number;
constructor(id: number, name: string, price: number) {
this.id = id;
this.name = name;
this.price = price;
}
public getPriceWithTax(taxRate: number = 0.1): number {
return Math.floor(this.price * (1 + taxRate));
}
}
// ===== Cart Class =====
class Cart {
private items: Product[] = [];
public addProduct(product: Product): void {
this.items.push(product);
}
public removeProduct(productId: number): void {
this.items = this.items.filter((item) => item.id !== productId);
}
public getItems(): Product[] {
return [...this.items];
}
public getTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
public getTotalWithTax(taxRate: number = 0.1): number {
return this.items.reduce(
(sum, item) => sum + item.getPriceWithTax(taxRate),
0
);
}
}
// ===== BankAccount Class =====
class BankAccount {
public readonly accountNumber: string;
public ownerName: string;
private balance: number;
constructor(accountNumber: string, ownerName: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
public deposit(amount: number): void {
if (amount <= 0) {
throw new Error("入金額は正の数である必要があります");
}
this.balance += amount;
}
public withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("出金額は正の数である必要があります");
}
if (amount > this.balance) {
throw new Error("残高不足です");
}
this.balance -= amount;
}
public getBalance(): number {
return this.balance;
}
}
// ===== 簡単な動作確認 =====
const user = new User(1, "Taro", "[email protected]");
console.log(user.greet());
const book = new Product(100, "TypeScript入門", 2000);
const pen = new Product(101, "ペン", 200);
const cart = new Cart();
cart.addProduct(book);
cart.addProduct(pen);
console.log("カート合計(税抜):", cart.getTotal());
console.log("カート合計(税込):", cart.getTotalWithTax());
const account = new BankAccount("123-456", "Taro", 1000);
account.deposit(500);
account.withdraw(300);
console.log("口座残高:", account.getBalance());
TypeScriptDay 26演習のまとめ
今回の演習では、
- User / Product / Cart / BankAccount という4つのクラスを通して、 Class / Constructor / Property / Method の基本を確認しつつ、
public/private/readonlyを使った「安全な設計」を体験しました。 - 特に、
- 「外から触ってほしくないものは
privateにする」 - 「一度決まったら変わらないものは
readonlyにする」 といった考え方は、実務でもそのまま役立つ重要なポイントです。
- 「外から触ってほしくないものは
この4つのクラスをベースに、
- Userに「権限」や「ステータス」を追加する
- Productに「在庫数」や「カテゴリ」を追加する
- Cartに「ユーザー紐づけ」や「クーポン適用」を追加する
- BankAccountに「履歴ログ」や「手数料計算」を追加する
など、少しずつ機能を広げていくと、 クラス設計の楽しさと奥行きが、自然と見えてくるはずです。
