반응형
메멘토 패턴이란?
객체 내부의 상태를 공개하지 않으면서 외부에 저장하여 객체의 상태를 이전 상태로 복원할 수 있도록 해주는 패턴이다.
즉, 캡슐화를 깨트리지 않고 내부 상태를 외부에 저장할 수 있도록 하는 패턴이다.
주로 되돌리기나 상태의 스탭샷에 사용된다!
메멘토 패턴을 사용해야 하는 이유?
- 객체의 이전 상태를 저장하고, 필요할 때 이 상태로 객체를 복원할 수 있다.
- 객체 내부 상태에 대한 접근을 제어해서 캡슐화를 유지할 수 있다. 따라서 외부에 객체의 상태를 공개하지 않고 저장하고 복원할 수 있다.
- 객체의 상태 관리를 단순화한다. 상태를 저장하고 복원하는 작업을 객체 외부에서 수행하여 객체 자체는 상태관리 로직으로부터 자유로워진다.
메멘토 패턴을 언제 사용해야 하는가?
- 되돌리기 기능을 제공해야 할 때
- 시간에 따라 변환하는 객체의 상태를 이력으로 남기고 필요에 따라 특정 상태로 되돌릴 수 있어야 할 때
- 디버깅, 테스트에서 각 시점별로 상태를 복원하여 다양한 시나리오를 재현할 필요가 있을 때
사용 시 문제점
- 객체의 상태를 여러 번 저장해야 하는 경우, 메모리가 많이 차지하게 될 수 있다
- 객체의 상태가 크거나 복잡한 경우 객체의 상태를 저장하고 복원하는 과정에서 시간이 걸려 성능 저하가 발생할 수 있다
- 메멘토 패턴을 잘못 구현하면 캡슐화를 위반할 수 있다.
- 메멘토 객체관리해야 하는 별도의 로직이 필요해 관리가 복잡해질 수 있다.
- 연관된 객체들의 상태를 함께 변경해야 하는 경우 시스템 전체의 일관성을 유지하는 것이 매우 복잡해질 수 있다.
메모리의 경우 모든 상태들을 저장하는 게 아닌 상태의 변경점만 저장하는 방법을 사용하면 최적화할 수 있다.
구현 예시
간단하게 게임 스테이지와 캐릭터의 상태를 관리하는 것을 메멘토 패턴으로 구현해 보았다.
먼저 현재 캐릭터의 상태를 저장할 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))
반응형
'Design Pattern > 행동 패턴(Behavioral patterns)' 카테고리의 다른 글
State Pattern, 상태 패턴 (0) | 2024.09.11 |
---|---|
Observer Pattern, 옵저버 패턴 (0) | 2024.04.01 |
Mediator Pattern, 중재자 패턴 (2) | 2024.03.07 |
Iterator Pattern, 반복자 패턴 (0) | 2024.02.28 |
Command Pattern, 커맨드 패턴 (1) | 2024.02.27 |