메멘토 패턴
객체의 상태를 이전의 상태로 복원할 수 있는 방법을 제공하는 디자인 패턴. 객체의 상태를 캡처하고 필요할 때 그 상태를 복원하는 기능을 통해, 객체의 내부 상태에 대한 직접적인 접근을 피하면서도 상태 정보를 외부에 저장하고 관리할 수 있다.
구조
- Memento - 객체의 내부 상태를 저장하는 클래스. 객체의 상태를 저장하거나 복원하기 위해 사용된다.
- Originator - 원조 객체. 메멘토를 생성하여 현재 객체의상태를 저장하고, 메멘토를 사용하여 내부 상태를 복원한다.
- Caretaker - 메멘토의 보관을 책임지는 클래스. 메멘토의 내부 상태를 읽거나 접근하지 못하고 단지 보관만 한다.
기본 인터페이스
public class Memento
{
public string State { get; private set; }
public Memento(string state)
{
State = state;
}
}
public class Originator
{
public string State { get; set; }
public Memento SaveStateToMemento()
{
return new Memento(State);
}
public void GetStateFromMemento(Memento memento)
{
State = memento.State;
}
}
public class Caretaker
{
private List<Memento> mementoList = new List<Memento>();
public void Add(Memento state)
{
mementoList.Add(state);
}
public Memento Get(int index)
{
return mementoList[index];
}
}
class Program
{
static void Main(string[] args)
{
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.State = "State #1";
originator.State = "State #2";
caretaker.Add(originator.SaveStateToMemento());
originator.State = "State #3";
caretaker.Add(originator.SaveStateToMemento());
originator.State = "State #4";
Console.WriteLine("Current State: " + originator.State);
originator.GetStateFromMemento(caretaker.Get(0));
Console.WriteLine("First saved State: " + originator.State);
originator.GetStateFromMemento(caretaker.Get(1));
Console.WriteLine("Second saved State: " + originator.State);
}
}
Current State: State #4
First saved State: State #2
Second saved State: State #3
왜 사용해야 할까?
메멘토 패턴은 우리가 문서 및 개발 관련 작성을 할 때 자주 쓰는 기능 중 하나인 ctrl+z, 즉 실행 취소를 디자인 패턴화한 것으로 생각하면 된다.
지금까지 만들었던 작업 내용을 없애고, 이전 작업 상태로 복원하려면 당연히 이전 작업 상태에 대한 데이터를 가지고 있어야 한다. 프로그램은 1.이전 작업을 저장하고 2.이전 작업을 불러와 3.현 작업 상태에 적용하는 과정을 거치게 된다.
메멘토 패턴은 현 상태를 가지고 있는 객체 Originator, 객체 상태를 저장하는 보존형 객체 Memento, 상태를 저장하고 관리하는 CareTaker로 구분하여 객체의 상태를 저장하고 복원하는 과정을 간편하게 처리할 수 있도록 돕는다.
그렇다면 왜 굳이 메멘토 패턴을 사용해야 할까? 이전 작업 내용들을 저장하는 방식은 여러 방식으로 해도 될 것이다. 그냥 기존 객체를 복사하여 저장하고, 해당 객체로 변경하면 되지 않을까?
메멘토 패턴을 사용하지 않을 경우, 객체의 상태 관리가 외부로 노출될 수 있다. 이는 객체의 내부 구조를 외부에 공개하게 되어 객체 지향 프로그래밍의 핵심 원칙인 캡슐화를 위반하는 것이다. 객체의 상태 정보에 직접 접근하면, 객체의 내부 구현이 외부에 의존적이 되어 수정 및 확장성에 문제가 발생할 수 있다. 캡슐화가 손상되면 객체의 안정성과 유지보수성이 저하되고, 객체의 상태가 예기치 않은 방식으로 변경될 위험이 증가한다.
아래 코드는 게임의 점수 상태를 저장하고 복원하는 과정을 보여준다. 하지만 이 코드는 캡슐화를 위반하는 방식으로 상태를 관리한다. Game 객체의 setRedTeamScore와 setBlueTeamScore 메서드를 통해 외부에서 직접 점수를 설정하고, getRedTeamScore 및 getBlueTeamScore 메서드를 사용하여 점수를 가져와 다른 Game 객체에 설정한다.
//상태 저장
Game game = new Game();
game.setRedTeamScore(10);
game.setBlueTeamScore(20);
//상태 복원
Game restoreGame = new Game();
//캡슐화 위반
restoreGame.setRedTeamScore(game.getRedTeamScore());
restoreGame.setBlueTeamScore(game.getBlueTeamScore());
이 방식은 Game 객체의 내부 상태(여기서는 팀 점수)가 외부에 노출되어 있어, Game 객체의 내부 구조에 대한 정보 없이는 상태를 관리할 수 없다. 이는 객체 지향 프로그래밍의 중요한 원칙인 캡슐화를 위반하는 것으로, 객체의 상태 관리가 외부에 의존하게 되고, 객체의 내부 구현 변경 시 외부 코드에도 영향을 미칠 수 있다. 또한, 이 방식은 상태 관리 로직이 분산되어 있어 시스템의 전체적인 복잡성을 증가시키며, 유지보수와 확장성에 문제를 일으킬 수 있다.
메멘토 패턴을 적용하면 위의 문제들을 해결할 수 있다. 메멘토 패턴은 Originator의 상태를 저장하는 Memento를 따로 생성하고, Memento를 관리하는 Caretaker를 분리함으로써 복잡성을 낮춤으로써 유지보수와 확장성을 보장한다. 또한, Memento를 통해 저장하고 복원하기 때문에 Originator나 Memento의 내부 로직을 확인할 수 없도록 캡슐화를 보장한다.
public class Game {
private int redTeamScore;
private int blueTeamScore;
public void SetRedTeamScore(int score) {
redTeamScore = score;
}
public void SetBlueTeamScore(int score) {
blueTeamScore = score;
}
public GameMemento SaveState() {
return new GameMemento(redTeamScore, blueTeamScore);
}
public void RestoreState(GameMemento memento) {
redTeamScore = memento.RedTeamScore;
blueTeamScore = memento.BlueTeamScore;
}
}
public class GameMemento {
public int RedTeamScore { get; private set; }
public int BlueTeamScore { get; private set; }
public GameMemento(int redScore, int blueScore) {
RedTeamScore = redScore;
BlueTeamScore = blueScore;
}
}
위 코드에서 Game 클래스는 메서드를 통해 현재 상태를 GameMemento 객체로 저장하고, RestoreState 메서드를 통해 저장된 상태를 복원한다. GameMemento 클래스는 Game 객체의 상태를 캡슐화하여 외부에 노출하지 않는다. 이 방식은 캡슐화를 유지하면서도 상태 관리의 필요성을 충족시킨다. Game 객체의 내부 구현은 외부에 은폐되어 있으며, 상태 관리는 완전히 Game 객체의 내부에 캡슐화된다. 이는 Game 객체의 변경이 외부 코드에 영향을 미치지 않도록 보장하며, 시스템의 복잡성을 줄이고 유지보수 및 확장성을 개선한다. 메멘토 패턴을 사용하면 객체의 상태를 안전하고 효율적으로 관리할 수 있으며, 객체의 상태 변경과 관련된 로직을 한 곳에 집중시켜 코드의 명확성과 관리 용이성을 향상시킨다.
Game game = new Game();
game.setRedTeamScore(10);
game.setBlueTeamScore(20);
GameSave save = game.Save();
Game restoreGame = new Game();
restoreGame.Restore(save);
장점
- 객체의 내부 상태에 대한 직접적인 접근 없이도 객체 상태의 스냅샷을 저장하고 복원할 수 있다.
- 객체의 캡슐화를 유지하면서도, 상태의 복원과 관리가 가능하다.
- 객체 상태가 변경되더라도 클라이언트 코드의 변화는 없다.
단점
- 많은 정보를 저장하는 Mementor를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있다.
참고자료
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
21. [Design Pattern] 옵저버 패턴(Observer) (1) | 2024.02.26 |
---|---|
20. [Design Pattern] 상태 패턴(State) (0) | 2024.02.20 |
18. [Design Pattern] 중재자(Mediator) 패턴 (0) | 2024.01.22 |
17. [Design Pattern] 전략(Strategy) 패턴 (0) | 2024.01.17 |
16. [Design Pattern] 이터레이터(Iterator) 패턴 (0) | 2024.01.14 |