👀

デザインパターン:Mementoパターン

2024/07/19に公開

これは何?

ある時点でのオブジェクトの内部状態のスナップショットをオブジェクトの「内部で」生成し後に信頼性高く復元する

何が嬉しいのか

問題

スマートホームシステムで,照明,温度,音楽などの特定の「シーン」の状態を保存しユーザーが「映画の夜」や「ディナーパーティー」などのシーンを一つの操作で再現できるようにしたい.

それぞれのスマートホームデバイスの値を他のオブジェクトが「外部から」 読み取り保存する方法をまずは考えるはず.

そのためには様々なデバイスの値の状態を読み取れるようにしておく必要があるが,

これはプライバシー的な観点で問題である(スマートホームデバイスなので,他の機器との連携が前提.場合によってはハッカーに不在を知らせることになる)

解決

スマートデバイスの 状態のスナップショット(Memento)の作成を, 他のオブジェクトが 「外部から」 スマートホームデバイスの状態をコピーしようとする代わりに、 自身の状態へ完全にアクセスできるスマートホームシステム自体のクラス(Originator、 発起人) に限定する.Mementoはケアテーカー(Caretaker)に保存し管理する.

class SmartHome {
    private lighting: string;
    private temperature: number;
    private music: string;

    constructor(lighting: string, temperature: number, music: string) {
        this.lighting = lighting;
        this.temperature = temperature;
        this.music = music;
    }

    // デバイスの操作
    changeState(lighting: string, temperature: number, music: string) {
        this.lighting = lighting;
        this.temperature = temperature;
        this.music = music;
    }

    // メメントを"内部で"作成する
    public save(): Memento {
        return new SmartHomeMemento(this.lighting, this.temperature, this.music);
    }

    // メメントから状態を復元する
    restore(memento: SmartHomeMemento): void {
        this.lighting = memento.getLighting();
        this.temperature = memento.getTemperature();
        this.music = memento.getMusic();
    }

    // 状態を表示する
    display(): void {
        console.log(`Lighting: ${this.lighting}, Temperature: ${this.temperature}, Music: ${this.music}`);
    }
}

interface Memento {
    getLighting(): string;
    getTemperature(): number;
    getMusic(): string;
}

// メメントクラス
class SmartHomeMemento implements Memento {
    private readonly lighting: string;
    private readonly temperature: number;
    private readonly music: string;

    constructor(lighting: string, temperature: number, music: string) {
        this.lighting = lighting;
        this.temperature = temperature;
        this.music = music;
    }

    getLighting(): string {
        return this.lighting;
    }

    getTemperature(): number {
        return this.temperature;
    }

    getMusic(): string {
        return this.music;
    }
}

class Caretaker {
    private scenes: { [name: string]: Memento } = {};
    private smartHome: SmartHome;

    constructor(smartHome: SmartHome) {
        this.smartHome = smartHome;
    }

    public backup(name: string): void {
        this.scenes[name] = this.smartHome.save();
    }

    public restore(name: string): void {
        if (this.scenes[name]) {
            this.smartHome.restore(this.scenes[name] as SmartHomeMemento);
        } else {
            console.log(`Scene "${name}" does not exist.`);
        }
    }
}

// 使用例
const smartHome = new SmartHome("Soft", 22, "Jazz");
const caretaker = new Caretaker(smartHome);
smartHome.display(); 

// 状態を保存
caretaker.backup("dinner");

// 状態を変更
smartHome.changeState("Hard", 28, "Rock");
smartHome.display();  // 変更後の状態を表示

// 状態を復元
caretaker.restore("dinner");
smartHome.display();  // 復元された状態を表示

その他の推しポイント

ケアテーカーはInterfaceを通してメメントにアクセスするためメメントは改竄されない

オブジェクトはメメントクラスの変更の影響を受けない(カプセル化されている)

動く実装例

https://www.typescriptlang.org/play?#code/PTAEnaGQfhkZ4ZDsGQ2p0NKGgkhkBYRhNBkPUMhLhkJ0MAoAYwBsBDAZ3NAGUBbUgJwBcAJAe1oFNQBvfUAaAAODAJYA3Uk27FRAcwAWTUQDs5ALlDkmYtQG5+gkRKndptIZwZSArg06aVN2gCMrBwcLGTpoWjfJRQk1tXTkDQwFCNhVQm0ImNgYACllFZTUQnVU5ABpQc0trJjsHUCdXK3z-QOCtbLUASl5IzwKFUXIAOjSlHNAAXlBejPDWzyYO7sKrW3tBgs4LWZL7DzaBSc6umqCF3cJ1gQBffFaQUEBxhkAFhkAShlxYQFmTQB15VsIFUjVOaiZTVPk+pl6mF8jNiqVHM43AxqgEglkws0+BtNlMegDRgsRjkjm0ttMlkU5twhmDibiJmiDvs4YdWqdzmBAIcMTOggAmGZAAIkAoYqAC4TAOYMnOegAQjdCAaIZWkIbC5ZIQtKRxJxko1NABZJacFSJFoo0D2VYqcqcADuNHozHYXHVXC1bGS+PR6RyoLRZNWnBd2wOjVxDM8FxZ7MA0gyASIYkMhAJX6gGFFcWtezaJJKrg2xKaOiMVgcTjWzWJFWgcRsUQAEx1KId2LU+w1tq6ck4TAAMhicsqKYIHW7StWU2w6w2ACqElalNvjDtU2k93N9+tMVW0seeP2CC7h1CALk9Y55i50hGQAJ7KzSFktljbRWJsYicHpsOTJAAGzadQIAJDwKy21Md8kPluC9iaB+nbDoBnC-qAC61MBn6TrUxyPj69L4AyqjSAwABmpCENwOa2ueoBzi+gIPvmoQ4q0c7-kS7rHuUULuFRDbQUE9EUfoqFnAGrJsjgBAkBQVDphaWb4dqogWDeKZUOJbCEcYPjcPYpDFjExAHsM34aMClGeIpph6pwqnqZpXZAQxlQMLiBm+CpakqBpfi0gilFvDEcQJEk-yvjpHF5IsAHEpCVmwjBulNIRlLbJWchYtp7aots5kkoFtGlIl7RelOQwHL6ZyeMR2nsQ0cXIii+p2IaX6+flzFMDRI72PRFTQlFgiVQw1WumB5IofVrGECVYTtQInXddltT5QygmUKAADCjANqQADWVgKd4hnkLhKicOQmg8KAADaKikFwrlqAAumqNbascCw8McNmbb45Dmpm51mhmlqcBEniXp5iQpG931Zmm70-Ui45Jd0IOiVwCxwx9v39fp0qyqALg4StNhCMkp2ff5+anqW5UbA622antJ1nZwl0LBTENZl05AKkqyHLgVRjo3s8ZA0qBNlETJ5FqT0OgKImGgPaaKU7t5A01wl1Q7q0Ww0zXBdHziYy9scvU4L9MUF98PZrdbAcyi92cMQ5DcGTqsA9et7EPeT7UDt3Cch+gvHJyoBqXt5RsEwoCcAAHp0TBdEhmWnJzDIXIA-vKABSugDR8kQHmh0jP0LLtpoicjySctQbCYUwnL5AATFX+ScgAUqQABeTecshAOh4QS2-GtDB5yaC3d6tVjJDnWbIWPmu7uQ+6kEePqgNxYDhoAu-KABraRBD73XRY4QON45yu4qLtDBtxEa4oIAkJqAC9m+CT7e7yfPWPx-JyLCMMWlegFXAAcdcAEpsD3mfO+GtbzT1nvPPQAgLg30ADH6CAUCbiXqAcM0ZN76mHgwLWe1+bFyPifEB98ugQMPG2GBYBoyAFUGQAMQyAH0GdcG58BAA

参考文献

https://refactoring.guru/ja/design-patterns/memento

Tokyo, inc. Engineers

Discussion