행동 관련 : 메멘토 패턴

    들어가기 전

    이 글은 백기선님의 GOF 디자인 패턴 강의를 공부하며 작성한 글입니다. 

     


    메멘토 패턴

    • GOF : 캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 방법. 
      • 클라이언트는 복원하고 싶을 때, 외부에 저장된 객체 내부 상태 정보에 어떤 필드가 있는지 몰라도 됨. 
      • 이 정보를 이용해 복원도 할 수 있게 됨. 
    • Component
      • Originator
        • 저장하고자 하는 본래의 상태를 의미함.  
      •  CareTaker
        • Originator의 내부 정보를 가지고 와서 저장하고 있음.  이 때 Originator의 내부 정보를 Memento 타입으로 가지고 있음. 
        • Originator의 내부 정보를 CareTaker를 통해서 복원할 수 있음. 
      • Memento
        • Originator의 내부 정보 추상화한 객체. Memento에 Originator의 내부 정보가 캡슐화 된채로 저장됨. 
        • Memento는 불변 객체로 구성되어야 함. (내부 정보가 한번 설정되면 변경되지 않도록) → final 클래스.

     

     


    Memento 패턴의 장단점

    • 장점
      • 객체 내부의 상태를 외부에 노출하지 않고, Memento 타입으로 캡슐화해서 객체 상태의 스냅샷을 남기고 복원할 수 있음. 
    • 단점
      • Memento 객체가 많은 정보를 담고 있고 자주 생성한다면 메모리 사용량에 영향을 줄 수 있음. 

    객체 내부 상태를 캡슐화된 채로 저장한다. 만약 캡슐화 되지 않았다면, 클라이언트가 내부 정보를 모두 알고 하나씩 복원해야 했을 것이다. 즉, 클라이언트가 특정 객체와 강하게 결합한다. 하지만 메멘토 패턴을 사용해서 이 부분을 분리해주기 때문에 클라이언트는 복원에 대한 자세한 로직을 몰라도 되게 된다. 

     


    기존 코드 

    public class Game implements Serializable {
        private int redTeamScore;
        private int blueTeamScore;
        public int getRedTeamScore() {return redTeamScore;}
        public void setRedTeamScore(int redTeamScore) {this.redTeamScore = redTeamScore;}
        public int getBlueTeamScore() {return blueTeamScore;}
        public void setBlueTeamScore(int blueTeamScore) {this.blueTeamScore = blueTeamScore;}
    }

    Game 클래스가 존재한다. 여기서 Game의 정보를 저장 및 복원하고 싶어하며, 이 때 대상은 다음과 같다.

    •  redTeamScore
    • blueTeamScore
    public class Client {
    
        public static void main(String[] args) {
            Game game = new Game();
            game.setRedTeamScore(10);
            game.setBlueTeamScore(20);
    
            int blueTeamScore = game.getBlueTeamScore();
            int redTeamScore = game.getRedTeamScore();
    
    		// 이전 상태로 복원. 
            Game restoredGame = new Game();
            restoredGame.setBlueTeamScore(blueTeamScore);
            restoredGame.setRedTeamScore(redTeamScore);
        }
    }

    Game의 상태를 저장하고 복원하는 코드다. 이 코드에서 문제점은 다음과 같다.

    • Client가 game의 내부 필드인 blueTeamScore, redTeamScore를 직접 불러와서 Setter를 이용해서 복원함. 
    • 즉, 캡슐화가 깨졌음을 의미함. 

    이것은 클라이언트가 Game의 상태를 복원하기 위한 모든 내부 정보를 다 알고 있음을 의미한다. 즉, 특정 시점의 스냅샷을 위해 어떤 정보를 저장해야하고 복원해야하는지 내부 로직에 강하게 결합된 상태이다. 이런 상태에서 게임 정보에 greenTeamScore까지 추가되면 내부 상태를 저장, 복원하는 코드 전체에 대한 변경이 필요해진다. 

     

     


    메멘토 패턴 적용

    메멘토 패턴은 다음을 정의해야한다.

    • CareTaker : Client 클래스
    • Originator : Game 클래스
    • Memento : Gamesave 클래스 

    Originator는 저장해야 할 정보, Memento는 저장해야 할 정보를 추상화 + 캡슐화한 객체다. 그리고 CareTaker는 Memento를 이용해 Originator의 정보를 복원해야 할 책임을 가진다. 이 때 Memento 클래스는 불변하는 정보를 가져야 하기 때문에 불변 객체로 만들어야 한다.

    따라서 Originator에는 save(), restore() 메서드를 추가하고, GameSave 클래스는 불변 객체로 생성한다. 

    public class Game implements Serializable {
        ...
        public GameSave save() {
            return new GameSave(this.redTeamScore, this.blueTeamScore);
        }
    
        public void restore(GameSave gameSave) {
            this.blueTeamScore = gameSave.getBlueTeamScore();
            this.redTeamScore = gameSave.getRedTeamScore();
        }
    }

    Game 클래스는 다음 메서드를 추가한다.

    • save() : 기존 게임 정보를 메멘토 객체로 저장하는 메서드.
    • restore() : 메멘토 객체를 이용해 기존 상태를 복원하는 메서드. 
    // 메멘토 객체는 불변 객체. 
    public final class GameSave {
        private final int redTeamScore;
        private final int blueTeamScore;
    
        public GameSave(int redTeamScore, int blueTeamScore) {
            this.redTeamScore = redTeamScore;
            this.blueTeamScore = blueTeamScore;
        }
    
        ...
    }

    GameSave는 메멘토 객체이며, 상태를 저장해야하기 때문에 불변 객체로 만든다.

    • final 클래스
    • final 필드
    public class Client {
    
        public static void main(String[] args) {
            Game game = new Game();
            game.setRedTeamScore(10);
            game.setBlueTeamScore(20);
    
            // Memento 생성 -> 여기서 값을 숨김채로 저장함.
            GameSave save = game.save();
    
            game.setBlueTeamScore(20);
            game.setRedTeamScore(30);
    
    		// 정보를 복원할 때, 캡슐화 된 정보를 사용함. 
            game.restore(save);
    
            System.out.printf("blue team : %d, read team : %d",
                    game.getBlueTeamScore(),
                    game.getRedTeamScore());
        }
    }

    여기서 CareTaker는 따로 구성하지 않았고, 대신 클라이언트를 이용해서 구현했다. 

    • 이전에는 이전 상태를 복원할 때 직접 setter를 이용했었는데, 캡슐화가 깨졌음을 의미한다. 
    • 변경된 이후에는 GameSave만 사용하기 때문에 클라이언트로부터 Game 정보의 캡슐화를 유지하면서 복원이 가능해진다. 

    '디자인 패턴' 카테고리의 다른 글

    행동 관련 : State 패턴  (0) 2023.11.30
    행동 관련 : 템플릿 메서드 패턴  (0) 2023.11.30
    구조 관련 : 프록시 패턴  (0) 2023.11.26
    구조 관련 : 파사드 패턴  (0) 2023.11.25
    행동 관련 : 옵저버 패턴  (1) 2023.11.25

    댓글

    Designed by JB FACTORY