TypeScriptで学ぶプログラミングの世界 Part 4 [アクセス修飾子とは? public? private? protected?]

みなさんTypeScriptで開発していますか?TypeScriptで開発する際，ReactやNextを使っている方ならあまりpublicやprivateといったアクセス修飾子を使わない方が多いかなと思いますが，クラスの依存関係を用いた開発をするなら必要な知識になります．現代のWeb開発において，TypeScriptはその型安全性と優れた開発体験から，多くの開発者に支持されています．特に大規模なアプリケーション開発では，コードの可読性や保守性が重要視される中で，TypeScriptのアクセス制御機能は欠かせない要素となっている．本記事では，TypeScriptを用いたアクセス制御の仕組みと動作原理を詳しく解説し，具体的な使用方法やオブジェクト指向との関連性についても深掘りします

> Javaならクラスベースなのでアクセス修飾子はよく目にしますね？

> 新年 2025年 1発目の投稿です！本年もよろしくお願いします！

# シリーズ TypeScriptで学ぶプログラミング言語の世界
**Part1** 手続型からオブジェクト指向へ

https://qiita.com/JavaLangRuntimeException/items/cadf49bb419076819963

**Part2** ORMってなんなんだ？SQLとオブジェクト指向のミスマッチを感じませんか？

https://qiita.com/JavaLangRuntimeException/items/89fb282cd00c7daf40e6

**Part3** プログラミングパラダイムの進化と革命：機械語からマルチパラダイムへ...

https://qiita.com/JavaLangRuntimeException/items/fa31d112f2d473748cfe

**Part5** 総称型(ジェネリクス・型パラメータ)とは？

https://qiita.com/JavaLangRuntimeException/items/24c3e80a69401bf9adec

**Part6** staticってなんなんだ？

https://qiita.com/JavaLangRuntimeException/items/3ec567955e3b0be8b58d


# 他のシリーズ記事
**TypeScriptを知らない方は以下の記事を参照してください**

https://qiita.com/JavaLangRuntimeException/items/5894391c08e0d8e28389

> この記事はチートシートとしてシリーズ化しています．これは様々な言語，フレームワーク，ライブラリなど開発技術の使用方法，基本事項，応用事例を網羅し，手引書として記載したシリーズです．git/gh，lazygit，docker，vim，typescript，プルリクエスト/マークダウン，ステータスコード，ファイル操作，OpenAI AssistantsAPI，Ruby/Ruby on Rails のチートシートがあります．上の記事に遷移した後，各種チートシートのリンクがあります.

**TypeScriptで学ぶプログラミングの世界**
プログラミング言語を根本的に理解するシリーズ

https://qiita.com/JavaLangRuntimeException/items/cadf49bb419076819963

**情報処理技術者試験合格への道 [IP・SG・FE・AP]**
情報処理技術者試験に出題されるコンピュータサイエンス用語の紹介や単語集

https://qiita.com/JavaLangRuntimeException/items/991be402099542ccb936

**IAM AWS User クラウドサービスをフル活用しよう！**
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/371a334f5a6e07035db5

**AWS UserのGCP浮気日記**
GCPの様子をAWSと比較して考えてみるシリーズ

https://qiita.com/JavaLangRuntimeException/items/527d99e774165a763180

**Project Gopher: Unlocking Go’s Secrets**
Go言語や標準ライブラリの深掘り調査レポート

https://qiita.com/JavaLangRuntimeException/items/dc45b412d3fbd2ccb9e8

**Gopher’s Journey: Exploring TCP Protocol**
Goを用いてTCPプロトコルを探求 & 作成するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/38091220106d86651d2b

## アクセス制御とは何か

アクセス制御とは，プログラム内のデータや機能へのアクセスを制限し，適切な権限を持つユーザーのみが特定の操作を行えるようにする仕組みである．これにより，データの不正な変更やセキュリティリスクを防ぎ，システム全体の安定性と信頼性を確保することができる．

TypeScriptでは，`public`，`private`，`protected`といったアクセス修飾子を用いて，クラス内のプロパティやメソッドの可視性を制御することが可能である．これにより，クラスの内部実装を隠蔽し，外部からの不正なアクセスを防ぐことができる．

## TypeScriptにおけるアクセス修飾子の基本

TypeScriptでは，以下の3つのアクセス修飾子が提供されている．

1. **public**: デフォルトのアクセス修飾子であり，クラスの外部からもアクセス可能である
2. **private**: クラス内からのみアクセス可能であり，外部からはアクセスできない
3. **protected**: クラス自身およびそのサブクラスからアクセス可能であり，外部からはアクセスできない 

これらの修飾子を適切に使用することで，クラスの設計をより堅牢にし，意図しない操作を防ぐことができる．

### publicの使用例

publicはデフォルトのアクセス修飾子であり，明示的に指定しなくてもクラスのメンバーはpublicとして扱われる．

```typescript
class Person {
    public name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    public greet(): void {
        console.log(`こんにちは，私は${this.name}です．`);
    }
}

const person = new Person('太郎', 30);
console.log(person.name); // 太郎
person.greet(); // こんにちは，私は太郎です．
```

この例では，nameとgreetメソッドがpublicとして定義されており，外部から自由にアクセスできる．

### privateの使用例
privateはクラス内部からのみアクセス可能であり，外部からの直接アクセスを防ぐ．

```typescript
class BankAccount {
    private balance: number;

    constructor(initialBalance: number) {
        this.balance = initialBalance;
    }

    public deposit(amount: number): void {
        if (amount > 0) {
            this.balance += amount;
            console.log(`${amount}円を預金しました．現在の残高は${this.balance}円です．`);
        } else {
            console.log("預金額は正の数でなければなりません．");
        }
    }

    public withdraw(amount: number): void {
        if (amount > 0 && amount <= this.balance) {
            this.balance -= amount;
            console.log(`${amount}円を引き出しました．現在の残高は${this.balance}円です．`);
        } else {
            console.log("引き出し額が不正です．");
        }
    }

    public getBalance(): number {
        return this.balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500); // 500円を預金しました．現在の残高は1500円です．
account.withdraw(200); // 200円を引き出しました．現在の残高は1300円です．
console.log(account.balance); // エラー: Property 'balance' is private and only accessible within class 'BankAccount'.
```
ここでは，balanceプロパティがprivateとして定義されており，外部から直接アクセスすることはできない．代わりに，depositやwithdrawメソッドを通じてのみ操作が可能である．

### protectedの使用例
protectedはクラス自身とそのサブクラスからアクセス可能であり，外部からはアクセスできない．

```typescript
class Animal {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    protected move(distance: number): void {
        console.log(`${this.name}は${distance}メートル移動しました．`);
    }
}

class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }

    public bark(): void {
        console.log('ワンワン!');
    }

    public moveDistance(distance: number): void {
        this.move(distance);
    }
}

const dog = new Dog('ポチ');
dog.bark(); // ワンワン!
dog.moveDistance(10); // ポチは10メートル移動しました．
console.log(dog.name); // エラー: Property 'name' is protected and only accessible within class 'Animal' and its subclasses.
```
この例では，Animalクラスのnameプロパティとmoveメソッドがprotectedとして定義されているため，Dogクラスからはアクセスできるが，外部からはアクセスできない．

## クラスとアクセス修飾子の実践例
アクセス修飾子を用いたクラス設計は，オブジェクト指向プログラミングの基本原則であるカプセル化を実現するために不可欠である．以下に，より複雑なクラス設計の例を示す．

```typescript
class Employee {
    public id: number;
    public name: string;
    private salary: number;
    protected department: string;

    constructor(id: number, name: string, salary: number, department: string) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.department = department;
    }

    public getSalary(): number {
        return this.salary;
    }

    protected setDepartment(newDepartment: string): void {
        this.department = newDepartment;
    }

    public displayInfo(): void {
        console.log(`ID: ${this.id}, Name: ${this.name}, Department: ${this.department}`);
    }
}

class Manager extends Employee {
    private teamSize: number;

    constructor(id: number, name: string, salary: number, department: string, teamSize: number) {
        super(id, name, salary, department);
        this.teamSize = teamSize;
    }

    public getTeamSize(): number {
        return this.teamSize;
    }

    public changeDepartment(newDepartment: string): void {
        this.setDepartment(newDepartment);
    }

    public displayManagerInfo(): void {
        this.displayInfo();
        console.log(`Team Size: ${this.teamSize}`);
    }
}

const manager = new Manager(1, '佐藤', 800000, '開発部', 10);
manager.displayManagerInfo();
// 出力:
// ID: 1, Name: 佐藤, Department: 開発部
// Team Size: 10

console.log(manager.salary); // エラー: Property 'salary' is private and only accessible within class 'Employee'.
manager.changeDepartment('マーケティング部');
manager.displayManagerInfo();
// 出力:
// ID: 1, Name: 佐藤, Department: マーケティング部
// Team Size: 10
```
この例では，Employeeクラスが基本的な社員情報を管理し，Managerクラスがそれを継承してさらにチームサイズを管理している．salaryプロパティはprivateとして定義されているため，外部から直接アクセスすることはできない．一方，departmentプロパティはprotectedとして定義されているため，Managerクラスからはアクセス可能である．

## オブジェクト指向との関連
オブジェクト指向プログラミング（OOP）は，ソフトウェア設計のパラダイムであり，データとその操作をオブジェクトとして捉える．TypeScriptはOOPの概念を強力にサポートしており，アクセス制御もその一環として重要な役割を果たす．

:::note info
**カプセル化**
カプセル化は，オブジェクトの内部状態を隠蔽し，外部からの不正なアクセスや変更を防ぐ原則である．TypeScriptのアクセス修飾子は，このカプセル化を実現するためのツールとして機能する．

**継承とアクセス制御**
継承を利用することで，既存のクラスから新しいクラスを派生させ，機能を拡張することができる．アクセス修飾子は，継承関係においても重要であり，基底クラスのメンバーの可視性を制御することができる．
:::

```typescript
class Vehicle {
    public make: string;
    public model: string;
    protected year: number;

    constructor(make: string, model: string, year: number) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    public displayInfo(): void {
        console.log(`Make: ${this.make}, Model: ${this.model}, Year: ${this.year}`);
    }
}

class Car extends Vehicle {
    private doors: number;

    constructor(make: string, model: string, year: number, doors: number) {
        super(make, model, year);
        this.doors = doors;
    }

    public getDoors(): number {
        return this.doors;
    }

    public displayCarInfo(): void {
        this.displayInfo();
        console.log(`Doors: ${this.doors}`);
    }
}

const car = new Car('Toyota', 'Corolla', 2020, 4);
car.displayCarInfo();
// 出力:
// Make: Toyota, Model: Corolla, Year: 2020
// Doors: 4

console.log(car.year); // エラー: Property 'year' is protected and only accessible within class 'Vehicle' and its subclasses.
```
この例では，Vehicleクラスが基本的な車両情報を管理し，Carクラスがそれを継承してさらにドア数を管理している．yearプロパティはprotectedとして定義されているため，Carクラスからはアクセス可能であるが，外部からはアクセスできない．

:::note info
**ポリモーフィズム**
ポリモーフィズムは，異なるクラスのオブジェクトが同一のインターフェースを通じて操作されることを可能にする概念である．TypeScriptでは，インターフェースや抽象クラスを用いることで，ポリモーフィズムを実現することができる．
:::

```typescript
interface Shape {
    area(): number;
}

class Rectangle implements Shape {
    private width: number;
    private height: number;

    constructor(width: number, height: number) {
        this.width = width;
        this.height = height;
    }

    public area(): number {
        return this.width * this.height;
    }
}

class Circle implements Shape {
    private radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    public area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

function printArea(shape: Shape): void {
    console.log(`面積は${shape.area()}です．`);
}

const rectangle = new Rectangle(5, 10);
const circle = new Circle(7);

printArea(rectangle); // 面積は50です．
printArea(circle);    // 面積は153.93804002589985です．
```
この例では，Shapeインターフェースを実装するRectangleクラスとCircleクラスが定義されている．printArea関数はShapeインターフェースを受け取り，具体的な形状に依存せずに面積を計算することができる．これにより，ポリモーフィズムを利用して柔軟なコード設計が可能となる．

## インターフェースとアクセス制御
TypeScriptでは，インターフェースを使用してクラスの構造を定義することができる．インターフェース自体にはアクセス修飾子を設定することはできないが，クラス側で実装する際にアクセス修飾子を活用することが可能である．

```typescript
interface IUser {
    username: string;
    login(input: string): boolean;
}

class User implements IUser {
    public username: string;
    private password: string;

    constructor(username: string, password: string) {
        this.username = username;
        this.password = password;
    }

    private validatePassword(input: string): boolean {
        return this.password === input;
    }

    public login(input: string): boolean {
        return this.validatePassword(input);
    }
}

const user = new User('john_doe', 'securepassword123');
console.log(user.username); // john_doe
console.log(user.password); // エラー: Property 'password' is private and only accessible within class 'User'.
user.login('securepassword123'); // true
```
この例では，IUserインターフェースがusernameプロパティとloginメソッドを定義している．Userクラスはこれを実装し，passwordプロパティをprivateとして隠蔽している．インターフェースを通じて公開するメンバーのみが外部からアクセス可能であり，内部の実装は隠蔽されている．

## プロパティのアクセサ
TypeScriptでは，プロパティのゲッターとセッターを定義することで，アクセス制御をさらに細かく行うことができる．これにより，プロパティの値に対する読み取りや書き込みの際に追加のロジックを実装することが可能である．

```typescript
class User {
    private _password: string;
    public username: string;

    constructor(username: string, password: string) {
        this.username = username;
        this._password = password;
    }

    private validatePassword(input: string): boolean {
        return this._password === input;
    }

    public get password(): string {
        // パスワードの取得を制限する場合
        throw new Error("Password cannot be accessed directly.");
    }

    public set password(newPassword: string) {
        if (newPassword.length >= 8) {
            this._password = newPassword;
        } else {
            throw new Error("Password must be at least 8 characters long.");
        }
    }

    public login(input: string): boolean {
        return this.validatePassword(input);
    }
}

const user = new User('alice', 'password123');
console.log(user.username); // alice
console.log(user.password); // エラー: Password cannot be accessed directly.
user.password = 'newpass'; // エラー: Password must be at least 8 characters long.
user.password = 'newpassword123'; // 成功
console.log(user.login('newpassword123')); // true
```
この例では，_passwordプロパティをprivateとして定義し，passwordのゲッターとセッターを公開している．ゲッターではパスワードの直接取得を防止し，セッターではパスワードの長さを検証している．これにより，パスワードの管理がより安全かつ柔軟になる．

## アクセス制御のベストプラクティス
アクセス制御を効果的に実装するためには，以下のベストプラクティスを遵守することが重要である．

- **最小限の公開**
クラスやモジュールの外部に公開するメンバーは最小限に留める．必要な機能のみを公開し，内部実装は隠蔽する．
- **一貫性のある命名規則**
プライベートメンバーにはアンダースコアを付けるなど，一貫性のある命名規則を採用することで，コードの可読性を向上させる．
- **インターフェースの活用**
インターフェースを使用して，クラスの外部に公開するメンバーを明確に定義する．これにより，実装の詳細を隠蔽し，柔軟性を高める．
- **アクセサの適切な使用**
ゲッターとセッターを適切に使用し，プロパティへのアクセスを制御する．必要に応じて，読み取り専用や書き込み専用のプロパティを設定する．

```typescript
class BankAccount {
    private balance: number;
    public accountNumber: string;
    private owner: string;

    constructor(accountNumber: string, owner: string, initialBalance: number = 0) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = initialBalance;
    }

    public deposit(amount: number): void {
        if (amount > 0) {
            this.balance += amount;
            console.log(`${amount}円を預金しました．現在の残高は${this.balance}円です．`);
        } else {
            console.log("預金額は正の数でなければなりません．");
        }
    }

    public withdraw(amount: number): void {
        if (amount > 0 && amount <= this.balance) {
            this.balance -= amount;
            console.log(`${amount}円を引き出しました．現在の残高は${this.balance}円です．`);
        } else {
            console.log("引き出し額が不正です．");
        }
    }

    public getBalance(): number {
        return this.balance;
    }
}

const account = new BankAccount('1234567890', '山田太郎', 5000);
account.deposit(2000); // 2000円を預金しました．現在の残高は7000円です．
account.withdraw(1000); // 1000円を引き出しました．現在の残高は6000円です．
console.log(account.getBalance()); // 6000
console.log(account.balance); // エラー: Property 'balance' is private and only accessible within class 'BankAccount'.
```
この例では，BankAccountクラスがbalanceとownerをprivateとして定義し，accountNumberをpublicとして公開している．depositとwithdrawメソッドを通じてのみbalanceを操作することができ，直接変更することはできない．これにより，アカウントの整合性を保つことができる．

## ジェネリクスとアクセス制御
TypeScriptのジェネリクスを活用することで，型安全性を保ちながら柔軟なアクセス制御を実現することが可能である．例えば，共通のインターフェースを持つ複数のクラスに対して，同一の操作を適用する場合に有効である．

```typescript
interface IResource {
    id: number;
    name: string;
}

class File implements IResource {
    public id: number;
    public name: string;
    private content: string;

    constructor(id: number, name: string, content: string) {
        this.id = id;
        this.name = name;
        this.content = content;
    }

    public getContent(): string {
        return this.content;
    }

    public setContent(newContent: string): void {
        this.content = newContent;
    }
}

class DatabaseRecord implements IResource {
    public id: number;
    public name: string;
    private data: any;

    constructor(id: number, name: string, data: any) {
        this.id = id;
        this.name = name;
        this.data = data;
    }

    public getData(): any {
        return this.data;
    }

    public setData(newData: any): void {
        this.data = newData;
    }
}

function updateResource<T extends IResource>(resource: T, newName: string): void {
    resource.name = newName;
}

const file = new File(1, 'document.txt', 'Hello, World!');
const record = new DatabaseRecord(2, 'user123', { email: 'user@example.com' });

updateResource(file, 'updated_document.txt');
updateResource(record, 'updated_user123');

console.log(file.name); // updated_document.txt
console.log(record.name); // updated_user123
```
この例では，IResourceインターフェースを実装するFileクラスとDatabaseRecordクラスが定義されている．updateResource関数はジェネリクスを用いており，IResourceを拡張する任意のクラスに対して適用可能である．これにより，異なるリソースタイプに対して一貫したアクセス制御を実現している．

## モジュールと名前空間
TypeScriptでは，モジュールや名前空間を使用してコードを整理し，アクセス制御を強化することができる．これにより，異なる部分のコードが互いに干渉し合わないようにし，可読性と保守性を向上させる．

```typescript
// UserModule.ts
export class User {
    private password: string;
    public username: string;

    constructor(username: string, password: string) {
        this.username = username;
        this.password = password;
    }

    private validatePassword(input: string): boolean {
        return this.password === input;
    }

    public login(input: string): boolean {
        return this.validatePassword(input);
    }
}

// main.ts
import { User } from './UserModule';

const user = new User('john_doe', 'securepassword123');
user.login('securepassword123'); // true
console.log(user.username); // john_doe
console.log(user.password); // エラー: Property 'password' is private and only accessible within class 'User'.
```
この例では，Userクラスがモジュールとしてエクスポートされており，他のファイルからインポートして使用されている．passwordプロパティはprivateとして定義されているため，外部から直接アクセスすることはできない．

## デコレーターとアクセス制御
TypeScriptのデコレーター機能を利用することで，クラスやメソッド，プロパティに対して追加のメタデータを付与し，アクセス制御を強化することができる．例えば，特定の権限を持つユーザーのみがメソッドを実行できるようにする場合に有効である．

```typescript
function AdminOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
        if (this.adminLevel > 1) {
            return originalMethod.apply(this, args);
        } else {
            console.log("管理者権限が必要です．");
        }
    }

    return descriptor;
}

class AdminUser extends User {
    public adminLevel: number;

    constructor(username: string, password: string, adminLevel: number) {
        super(username, password);
        this.adminLevel = adminLevel;
    }

    @AdminOnly
    public accessAdminPanel(): void {
        console.log("管理者パネルにアクセスしました．");
    }
}

const admin = new AdminUser('admin_user', 'adminpass', 2);
admin.accessAdminPanel(); // 管理者パネルにアクセスしました．

const regularUser = new AdminUser('regular_user', 'userpass', 1);
regularUser.accessAdminPanel(); // 管理者権限が必要です．
```
この例では，AdminOnlyデコレーターがaccessAdminPanelメソッドに適用されている．メソッドの実行前にユーザーのadminLevelをチェックし，権限が不足している場合にはメソッドの実行をブロックする．これにより，アクセス制御をメソッド単位で柔軟に実装することができる．

TypeScriptのアクセス制御とデザインパターン
TypeScriptのアクセス制御機能は，さまざまなデザインパターンと組み合わせて使用することで，コードの再利用性と柔軟性を高めることができる．以下にいくつかの例を示す．

## シングルトンパターン
シングルトンパターンは，クラスのインスタンスが1つだけ存在することを保証するパターンである．TypeScriptでは，privateコンストラクタとpublicな静的メソッドを組み合わせて実装する．

```typescript
class Singleton {
    private static instance: Singleton;
    private constructor() {
        // 初期化処理
    }

    public static getInstance(): Singleton {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }

    public doSomething(): void {
        console.log("Singletonのメソッドが呼び出されました．");
    }
}

const singleton = Singleton.getInstance();
singleton.doSomething(); // Singletonのメソッドが呼び出されました．
```
この例では，Singletonクラスのコンストラクタがprivateとして定義されているため，外部から直接インスタンスを作成することはできない．getInstanceメソッドを通じてのみインスタンスにアクセスすることができる．

## ファクトリーパターン
ファクトリーパターンは，オブジェクトの生成を専門化するパターンである．TypeScriptでは，クラスメソッドや別のクラスを使用して実装することができる．

```typescript
interface IProduct {
    name: string;
    price: number;
}

class ConcreteProductA implements IProduct {
    public name: string;
    public price: number;

    constructor() {
        this.name = "Product A";
        this.price = 1000;
    }
}

class ConcreteProductB implements IProduct {
    public name: string;
    public price: number;

    constructor() {
        this.name = "Product B";
        this.price = 2000;
    }
}

class ProductFactory {
    public static createProduct(type: string): IProduct {
        if (type === "A") {
            return new ConcreteProductA();
        } else if (type === "B") {
            return new ConcreteProductB();
        } else {
            throw new Error("無効な製品タイプです．");
        }
    }
}

const productA = ProductFactory.createProduct("A");
const productB = ProductFactory.createProduct("B");
console.log(productA.name, productA.price); // Product A 1000
console.log(productB.name, productB.price); // Product B 2000
```
この例では，ProductFactoryクラスが製品の生成を担当している．アクセス制御により，製品の具体的なクラスは外部から直接インスタンス化できず，ファクトリーを通じてのみ生成される．

## オブザーバーパターン
オブザーバーパターンは，あるオブジェクトの状態が変化した際に，それに依存するオブジェクトに通知を行うパターンである．TypeScriptでは，イベントリスナーやコールバック関数を使用して実装することができる．

```typescript
interface Observer {
    update(message: string): void;
}

class Subject {
    private observers: Observer[] = [];

    public subscribe(observer: Observer): void {
        this.observers.push(observer);
    }

    public unsubscribe(observer: Observer): void {
        this.observers = this.observers.filter(obs => obs !== observer);
    }

    public notify(message: string): void {
        this.observers.forEach(observer => observer.update(message));
    }

    public changeState(message: string): void {
        console.log(`Subject: 状態が変更されました: ${message}`);
        this.notify(message);
    }
}

class ConcreteObserver implements Observer {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    public update(message: string): void {
        console.log(`${this.name}が通知を受け取りました: ${message}`);
    }
}

const subject = new Subject();
const observer1 = new ConcreteObserver('Observer1');
const observer2 = new ConcreteObserver('Observer2');

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.changeState('新しい状態'); 
// 出力:
// Subject: 状態が変更されました: 新しい状態
// Observer1が通知を受け取りました: 新しい状態
// Observer2が通知を受け取りました: 新しい状態
```
この例では，Subjectクラスが状態の変更を管理し，Observerインターフェースを実装するConcreteObserverクラスが通知を受け取る．アクセス制御を適切に使用することで，オブジェクト間の依存関係を明確にし，柔軟な設計を実現している．

## enumをクラスを用いて作成する
TypeScript で列挙型 (Enum) をクラスを用いて実現することができる．この方法では，列挙値を静的プロパティとして定義し，インスタンスを生成することで列挙型の機能を実現している．LION や TIGER などは静的プロパティとして定義され，コンストラクタで個々のインスタンスを作成する．また，生成されたすべてのインスタンスは _instances 配列に保持され，列挙型全体の管理が可能である．クラスを使うことで，equals メソッドやゲッターなどの追加機能を容易に実装できる点が利点である．

:::note warn
**列挙型の問題点**
**1. TypeScript独自の構文**
TypeScriptの列挙型は，JavaScriptに存在しない構文であり，TypeScriptの設計思想（JavaScriptとの親和性を重視）から外れたものとされている.列挙型はJavaScriptのオブジェクトに変換され，余計なコードが生成されるため，JavaScript開発者には馴染みにくい場合がある.

**2. 数値列挙型の型安全性の問題**
TypeScript 5.0未満では，数値列挙型においてnumber型であれば任意の値を代入できてしまう.
```typescript
enum ZeroOrOne {
  Zero = 0,
  One = 1,
}
const value: ZeroOrOne = 9; // TypeScript 5.0未満ではコンパイルエラーが起きない
```

**3. 双方向マッピングによる意図しない挙動**
列挙型は値から名前への双方向マッピングを持つため，未定義の値でもアクセスが可能になり，意図しないバグが発生する可能性がある.

```typescript
enum ZeroOrOne {
  Zero = 0,
  One = 1,
}
console.log(ZeroOrOne[9]); // コンパイルエラーではなくundefinedが出力される
```
**4. 文字列列挙型の公称型**
TypeScriptの型システムは構造的部分型を採用しているが，文字列列挙型は例外的に公称型になる.このため，同じ文字列リテラルでも代入可能性に差が生じ，混乱を招く場合がある.

```typescript
enum StringEnum {
  Foo = "foo",
}
const foo: StringEnum = "foo"; // コンパイルエラー
```

> `参考`
> [列挙型(enum)の問題点と代替手段 - サバイバルTypeScript](https://typescriptbook.jp/reference/values-types-variables/enum/enum-problems-and-alternatives-to-enums)

:::
```ts
export class AnimalEnum {
    private static readonly _instances: AnimalEnum[] = [];

    public static readonly LION = new AnimalEnum('Lion Output', 'lion');
    public static readonly TIGER = new AnimalEnum('Tiger Output', 'tiger');
    public static readonly ELEPHANT = new AnimalEnum('Elephant Output', 'elephant');

    private constructor(
        private readonly outputName: string,
        private readonly name: string,
    ) {
        AnimalEnum._instances.push(this);
    }

    // 一致するかどうかのメソッド
    public equals(other: AnimalEnum): boolean {
        return this.name === other.name;
    }

    // ゲッター
    public get OutputSystemName(): string {
        return this.outputSystemName;
    }

    public get SystemName(): string {
        return this.systemName;
    }
}

```

TypeScriptを用いることで，アクセス制御を効果的に実装し，オブジェクト指向の原則に則った堅牢なアプリケーションを構築することが可能である．アクセス修飾子やクラスの継承，インターフェース，デコレーターなどの機能を適切に活用することで，コードの可読性と保守性を向上させることができる．また，デザインパターンと組み合わせることで，さらに柔軟で再利用性の高い設計を実現することができる．TypeScriptの強力な型システムとアクセス制御機能を最大限に活用し，品質の高いソフトウェア開発を目指すべきである．