본문 바로가기
Design Pattern/행동 패턴(Behavioral patterns)

Memento Pattern, 메멘토 패턴

by codeyaki 2024. 3. 27.
반응형

메멘토 패턴이란?

객체 내부의 상태를 공개하지 않으면서 외부에 저장하여 객체의 상태를 이전 상태로 복원할 수 있도록 해주는 패턴이다. 

즉, 캡슐화를 깨트리지 않고 내부 상태를 외부에 저장할 수 있도록 하는 패턴이다.

주로 되돌리기나 상태의 스탭샷에 사용된다!

 

메멘토 패턴을 사용해야 하는 이유?

  • 객체의 이전 상태를 저장하고, 필요할 때 이 상태로 객체를 복원할 수 있다.
  • 객체 내부 상태에 대한 접근을 제어해서 캡슐화를 유지할 수 있다. 따라서 외부에 객체의 상태를 공개하지 않고 저장하고 복원할 수 있다.
  • 객체의 상태 관리를 단순화한다. 상태를 저장하고 복원하는 작업을 객체 외부에서 수행하여 객체 자체는 상태관리 로직으로부터 자유로워진다.

메멘토 패턴을 언제 사용해야 하는가?

  • 되돌리기 기능을 제공해야 할 때
  • 시간에 따라 변환하는 객체의 상태를 이력으로 남기고 필요에 따라 특정 상태로 되돌릴 수 있어야 할 때
  • 디버깅, 테스트에서 각 시점별로 상태를 복원하여 다양한 시나리오를 재현할 필요가 있을 때

사용 시 문제점

  • 객체의 상태를 여러 번 저장해야 하는 경우, 메모리가 많이 차지하게 될 수 있다
  • 객체의 상태가 크거나 복잡한 경우 객체의 상태를 저장하고 복원하는 과정에서 시간이 걸려 성능 저하가 발생할 수 있다
  • 메멘토 패턴을 잘못 구현하면 캡슐화를 위반할 수 있다.
  • 메멘토 객체관리해야 하는 별도의 로직이 필요해 관리가 복잡해질 수 있다.
  • 연관된 객체들의 상태를 함께 변경해야 하는 경우 시스템 전체의 일관성을 유지하는 것이 매우 복잡해질 수 있다.
메모리의 경우 모든 상태들을 저장하는 게 아닌 상태의 변경점만 저장하는 방법을 사용하면 최적화할 수 있다.

 

구현 예시

간단하게 게임 스테이지와 캐릭터의 상태를 관리하는 것을 메멘토 패턴으로 구현해 보았다.

먼저 현재 캐릭터의 상태를 저장할 VO를 만들어 준다.

package com.example.designpattern.behavioral.memento.game;


import lombok.EqualsAndHashCode;
import lombok.ToString;

@ToString
@EqualsAndHashCode
public class CharacterStatus {
    private final int hp;
    private final int mp;
    private final int atk;
    private final int def;
    private final int sp;

    public CharacterStatus() {
        this.hp = 1000;
        this.mp = 500;
        this.atk = 5;
        this.def = 0;
        this.sp = 10;
    }

    public CharacterStatus(int hp, int mp, int atk, int def, int sp) {
        this.hp = hp;
        this.mp = mp;
        this.atk = atk;
        this.def = def;
        this.sp = sp;
    }

    public CharacterStatus levelUp() {
        return new CharacterStatus(this.hp + 100, this.mp + 50, this.atk + 10, this.def + 5, this.sp +5);

    }

}

 

다음으로 Game의 상태를 저장할 객체를 만들어 준다.

package com.example.designpattern.behavioral.memento.game;

import com.example.designpattern.behavioral.memento.memento.GameSave;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@AllArgsConstructor
@Getter
@Setter
@ToString
public class Game {
    private int stage;
    private CharacterStatus characterStatus;

    public GameSave backup() {
        return new GameSave(this.stage, characterStatus);
    }

    public void restore(GameSave gameSave) {
        this.stage = gameSave.getStage();
        this.characterStatus = gameSave.getCharacterStatus();
    }
}

 

이때 게임은 자신의 상태를 백업할 수 있는 backup메서드를 가지고 있으며 이를 통해 메멘토 객체인 GameSave를 생성한다. 그리고 restore기능을 통해서 메멘토객체로부터 당시 시점의 상태를 불러올 수 있다.

 

다음으로 메멘토 객체를 구현해 보자

package com.example.designpattern.behavioral.memento.memento;

import com.example.designpattern.behavioral.memento.game.CharacterStatus;

public class GameSave {
    private final int stage;
    private final CharacterStatus characterStatus;

    public GameSave(int stage, CharacterStatus characterStatus) {
        this.stage = stage;
        this.characterStatus = characterStatus;
    }

    public int getStage() {
        return stage;
    }

    public CharacterStatus getCharacterStatus() {
        return characterStatus;
    }
}

메멘토 객체는 불변이므로 VO로 만들자 상태들을 final로 만들고 set을 할 수없도록 해야 한다.

 

마지막으로 이들을 관리할 GameManager를 만들어주었다.

package com.example.designpattern.behavioral.memento.memento;

import com.example.designpattern.behavioral.memento.game.Game;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
public class GameManager {
    private Game game;
    private GameSave[] gameSaveList;

    public GameManager(Game game) {
        this.game = game;
        this.gameSaveList = new GameSave[3];
    }

    public void gameSave(int slot) {
        if (slot < 0 || slot > 3) {
            System.out.println("0 ~ 3가능합니다.");
        }
        gameSaveList[slot] = game.backup();
    }

    public void gameLoad(int slot) {
        if (slot < 0 || slot > 3) {
            System.out.println("0 ~ 3가능합니다.");
        }
        game.restore(gameSaveList[slot]);
    }

    public void nextStage() {
        game.setStage(game.getStage() + 1);
        game.setCharacterStatus(game.getCharacterStatus().levelUp());
    }

    public void printCurrentGame() {
        System.out.println(game);
    }


}

현재 게임 상태확인, 다음 스테이지로 넘어가기, 게임 저장, 게임 로드 기능을 추가해 두었다. 이때 게임 저장 슬롯은 총 3개 제한해 두었다.

 

마지막으로 클라이언트 코드를 확인해 보자.

package com.example.designpattern.behavioral.memento;

import com.example.designpattern.behavioral.memento.game.CharacterStatus;
import com.example.designpattern.behavioral.memento.game.Game;
import com.example.designpattern.behavioral.memento.memento.GameManager;

public class MementoClient {
    public static void main(String[] args) {
        Game game = new Game(0, new CharacterStatus());
        GameManager gameManager = new GameManager(game);

        gameManager.printCurrentGame();
        gameManager.gameSave(2);

        gameManager.nextStage();
        gameManager.nextStage();
        gameManager.nextStage();
        gameManager.nextStage();

        gameManager.printCurrentGame();

        gameManager.gameLoad(2);

        gameManager.printCurrentGame();

    }
}

 

사용자 입력을 받아서 구현하는 방법으로 할 수도 있지만 현재는 로직을 확인하기 위해서 이렇게 구현하였다.

클라이언트 쪽에서는 게임의 구조를 모르지만 간편한 게 게임의 상태를 저장하고 불러올 수 있게 되었다.

실행 결과는 다음과 같다.

Game(stage=0, characterStatus=CharacterStatus(hp=1000, mp=500, atk=5, def=0, sp=10))
Game(stage=4, characterStatus=CharacterStatus(hp=1400, mp=700, atk=45, def=20, sp=30))
Game(stage=0, characterStatus=CharacterStatus(hp=1000, mp=500, atk=5, def=0, sp=10))

 

반응형