디자인 패턴(구)/행위 패턴

메멘토(Memento) 패턴이란?

공대키메라 2022. 4. 23. 23:16

1.메멘토 패턴이란?

이 단어를 인터넷에서 검색해봤다.

 

출처 주소 : https://en.dict.naver.com/#/entry/enko/995d2451fdc048af8bf19073c0334361

무언가를 기억하기 위한 방법이라는 것 같다. 짐작이 맞는지 한 번 알아가보자.

의도

캡슐화를 위배하지 않은 채 어떤 객체의 내부 상태를 잡아내고 실체화시켜 둠으로써, 이후 해당 객체가 그 상태로 되돌아올 수 있도록 한다. 

 

즉, 쉽게 객체의 이전 상태를 저장하는 방법이다. 

사용 시기

  • 어떤 객체의 상태에 대한 스냅샷(몇 개의 일부)을 저장한 후 나중에 이 상태로 복구해야 할 때
  • 상태를 얻는 데 필요한 직접적인 인터페이스를 두면 그 객체의 구현 세부사항이 드러날 수밖에 없고, 이것으로 객체의 캡슐화가 깨질 때

구조

  • Memento : 원조본 객체의 내부 상태를 저장
  • Originator :  원조본 객체. 메멘토를 생성해 현재 객체의 상태를 저장, 메멘토를 사여앟여 내부 상태 복원
  • Caretaker : 메멘토의 보관을 책임지는 보관자

구조를 보면 CareTaker가 Originator를 참조하고 있다. Originator는 Memento를 참조하고 있다.

CareTaker는 Menento를 참조하면서, Memento의 입장에서는 CareTaker가 집합 관계이다. 

즉, 집합 연관관계에 있다.

 

Caretaker는 메멘토의 보관을 책임지는 보관자인데, Memento를 여러개 가질 수 있다고 보면 된다. 

2. 구현 1 - game save, restore

구현(Implementation) 동화

공대키메라가 게임을 하려고 한다.

팀은 A팀, B팀으로 나뉘어 있다. 

우리의 어린 공대키메라! 엄마의 부름으로 잠시 게임을 저장했다.

거사(?)를 치른 후 다시 돌아온 키메라는 게임을 하기 시작했다.

그런데 결과가 마음에 들지 않는다. 급하게 다녀와서 그런가 말이다.

그래서 심부름을 할 때로 다시 세팅을 돌리려고 한다. 

Game.java

public class Game {

    private int teamAScore;
    private int teamBScore;

    //getter  & setter 생략

    public GameSave save() {
        return new GameSave(this.teamAScore, this.teamBScore);
    }

    public void restore(GameSave gameSave){
        this.teamAScore = gameSave.getaTeamScore();
        this.teamBScore = gameSave.getbTeamScore();
    }

}

GameSave.java

public final class GameSave {

    private final int teamAScore;

    private final int teamBScore;

    public GameSave(int blueTeamScore, int redTeamScore) {
        this.teamBScore = blueTeamScore;
        this.teamAScore = redTeamScore;
    }

    public int getTeamBScore() {
        return teamBScore;
    }

    public int getTeamAScore() {
        return teamAScore;
    }
}

Main.java

public class Main {

    public static void main(String[] args) {
        Game game = new Game();
        game.setTeamAScore(123);
        game.setTeamBScore(345);

        GameSave save = game.save();

        game.setTeamAScore(555);
        game.setTeamBScore(999);

        game.restore(save);

        System.out.println(game.getTeamAScore());
        System.out.println(game.getTeamBScore());

    }
}

 

사실 이 예제는 너무 날로먹은것 같은 느낌이 허다하다. 

 

의도는 이전 상태를 저장하는 것이 목적이라고 해서 이에 충실한것이라고는 하나 무언가... 좀 더 디테일했으면 한다.

 

그래서 다른 것도 찾아보았다.

 

youtube에서 또 다른 예를 찾았다.

2. 구현 2 

이 코드는 다음 사이트에서 참고했다.

 

출처 : https://www.youtube.com/watch?v=jOnxYT8Iaoo 

 

위의 영상을 보고 안내해주는 사이트로 가보면 이미 코드를 전부 무료로 지원하고 있다.

 

이 예제가 좋은 점은 구조대로 전부 구현을 했고, java swing을 이용해서 시각적으로 좀 더 이해하기 좋도록 도와주기 때문이다.

CareTaker.java

import java.util.ArrayList;

public class CareTaker {
    ArrayList<Memento> savedArticles = new ArrayList<>();
    public void addMemento(Memento m) {
        savedArticles.add(m);
    }
    public Memento getMemento(int index){
        return savedArticles.get(index);
    }
}

CareTaker의 경우 Memento를 참조하고 Memento 입장에서는 이것이 집합관계이다. 

 

그것을 CakeTaker에서는 List로 구현한 것으로 집합임을 확인했다. 이를 통해 실제로 메멘토의 보관을 책임지고 있다.

Memento.java

public class Memento {
    private String article;
    Memento(String articleSave) {
        article = articleSave;
    }
    public String getSavedArticle(){
        return article;
    }
}

들어오는 article내용을 저장하고 있다. 즉, 원조본 객체의 내부 상태를 저장하고 있다. 원조본 객체는 Originator인데 이것을 받아서 저장하는 것이다. 

Originator.java

public class Originator {

    private String article;
    
    public void set(String newArticle){
        System.out.println("From Originator : Current Version of Article\n" +
                newArticle + " ]n");

        article = newArticle;
    }

    public Memento storeInMemento(){
        System.out.println("From Originator : saving to Memento");

        return new Memento(article);
    }

    public String restoreFromMemento(Memento memento){
        article = memento.getSavedArticle();

        System.out.println("From Originator: Previous Article Saved in Memento\n" +
                article + "\n");

        return article;
    }

}

 

Originator는 원조본 객체이다. 메멘토를 생성해 현재 객체의 상태를 저장, 메멘토를 사용하여 내부 상태 복원한다.

TestMemento.java

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class TestMemento extends JFrame{
    public static void main(String[] args) {
        new TestMemento();
    }

    private JButton saveBut, undoBut, redoBut;
    private JTextArea theArticle = new JTextArea(40,60);

    CareTaker CareTaker = new CareTaker();
    Originator originator = new Originator();
    int saveFiles = 0, currentArticle = 0;

	//java swing 구현부분. 그냥 그러려니 하자.
    public TestMemento(){
        this.setSize(750,780);
        this.setTitle("Memento Design Pattern");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel1 = new JPanel();
        panel1.add(new JLabel("Article"));
        panel1.add(theArticle);

        ButtonListener saveListener = new ButtonListener();
        ButtonListener undoListener = new ButtonListener();
        ButtonListener redoListener = new ButtonListener();

        saveBut = new JButton("Save");
        saveBut.addActionListener(saveListener);

        undoBut = new JButton("Undo");
        undoBut.addActionListener(undoListener);

        redoBut = new JButton("Redo");
        redoBut.addActionListener(redoListener);

        panel1.add(saveBut);
        panel1.add(undoBut);
        panel1.add(redoBut);

        // Add panel to the frame that shows on screen
        this.add(panel1);
        this.setVisible(true);

    }

    class ButtonListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            if(e.getSource() == saveBut){
                // java swing에 입력한 text 받기
                String textInTextArea = theArticle.getText();
                //originator에 저장.
                originator.set(textInTextArea);

                // 원조본 객체를 careTaker에 저장. list에 쌓임
                CareTaker.addMemento( originator.storeInMemento() );

                saveFiles++;
                currentArticle++;

                System.out.println("Save Files " + saveFiles);

				//java Swing 버튼 사용, 미사용 조건 부여부분
                undoBut.setEnabled(true);

            } else

            if(e.getSource() == undoBut){
                if(currentArticle >= 1){
                    currentArticle--;
                    //저장된 내용 꺼냄. 
                    String textBoxString = originator.restoreFromMemento( CareTaker.getMemento(currentArticle) );
                    theArticle.setText(textBoxString);
					//java Swing 버튼 사용, 미사용 조건 부여부분
                    redoBut.setEnabled(true);
                } else {
 	               //java Swing 버튼 사용, 미사용 조건 부여부분
                    undoBut.setEnabled(false);
                }
            } else

            if(e.getSource() == redoBut){
                if((saveFiles - 1) > currentArticle){
                    currentArticle++;
                    String textBoxString = originator.restoreFromMemento( CareTaker.getMemento(currentArticle) );
                    theArticle.setText(textBoxString);
                    // Make undo clickable
                    undoBut.setEnabled(true);
                } else {
                    // Don't allow user to click Redo
                    redoBut.setEnabled(false);
                }
            }
        }
    }
}

 

 

실행하면 다음과 같다.

 

한번 저장하고 다시 다른 값을 추가해 저장했다.

이런식으로 꺼내오기, 불러오기, 취소하기를 반복할 수 있다.

 

careTaker에서 값을 가져오고 저장하고 하는 것이다. 

 

4. 간단한 고찰

그러면... 우리가 처음에 본 의도대로 되었는가?

 

의도를 다시 생각해보자.

 

의도

캡슐화를 위배하지 않은 채 어떤 객체의 내부 상태를 잡아내고 실체화시켜 둠으로써, 이후 해당 객체가 그 상태로 되돌아올 수 있도록 한다. 

 

캡슐화를 위배하지 않았는가?

 

다른 작업을 추가한다고 해서 다른 class들의 수정이 생기지는 않는거 같으니 캡슐화는 잘 되있는것 같다. 

 

객체가 다시 전의 상태로 되돌아 오는 것은 우리가 눈으로 확인했다.

 


이렇게 메멘토 패턴에 대해 알아보았다.

 

부족하거나 무언가 더 개선하면 좋은 사항은 피드백을 주면 감사하겠습니다. 꾸벅스