2020년 7월 8일 수요일

GOF Design Pattern - Memento







설명


객체의 상태의 스냅샷을 저장하기 위한 패턴.


객체의 상태를 하드코딩을 해서 저장하고 복구시키면 캡슐화 원칙이 깨짐.

그래서 Memento 라는 캑체를 만들어서 그 객체를 통해서 상태를 저장하고 복구함.

그리고 Memento는 객체의 상태와 관련된 메소드는 Originator 에게만 열려있고 나머지는 다 닫혀있어야 함.


문제는 Memento 저장 및 보관하는데 비용이 든다는 것.

또한 Originator 의 크기가 너무 큰경우 Memento 를 만드는 데에도 비용이 크다는 것.

이 경우는 점증적인 변화를 저장하기 위해 Command 와 함께 사용되어서 Memento 가 저장하는 객체의 상태가 Command Pool 로서 구현될 수도 있음.



예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Graphic
{
public:
    void Move(const int& delta) { _position += delta; std::cout << delta << " : Move " << std::endl; }
    int GetPosition() { return _position; }
 
private:
    int _position = 0;
};
 
class MoveCommand
{
public:
    MoveCommand(Graphic* target, const int& delta) : _state(nullptr), _delta(delta), _target(target) {}
    void Execute();
    void Unexecute();
 
private:
    class ConstraintSolverMemento* _state;
    int _delta;
    Graphic* _target;
};
 
 
class ConstraintSolver
{
public:
    static ConstraintSolver* Instance() { static ConstraintSolver solver; return &solver; }
 
    void Solve();
 
    void SetConstraint(Graphic* start, Graphic* end);
    std::string GetConstraint() { return _currentConstraint; }
 
    class ConstraintSolverMemento* CreateMemento();
    void SetMementoAndSolve(ConstraintSolverMemento* _state);
 
private:
    std::string _currentConstraint;
    Graphic* _start;
    Graphic* _end;
};
 
class ConstraintSolverMemento
{
public:
    virtual ~ConstraintSolverMemento() = default;
private:
    friend class ConstraintSolver;
    ConstraintSolverMemento() = default;
 
private:
    std::string _currentConstraint;
};
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void ConstraintSolver::Solve()
{
    _currentConstraint = std::string(
        std::to_string(_start->GetPosition())
        + " --- "
        + std::to_string(_end->GetPosition())
        + "  : time  " + GetCurrentTime()
    );
}
 
void ConstraintSolver::SetConstraint(Graphic* start, Graphic* end)
{
    _start = start; _end = end;
    Solve();
}
 
ConstraintSolverMemento* ConstraintSolver::CreateMemento()
{
    auto m = new ConstraintSolverMemento();
    m->_currentConstraint = _currentConstraint;
    return m;
}
 
void ConstraintSolver::SetMementoAndSolve(ConstraintSolverMemento* _state)
    _currentConstraint = _state->_currentConstraint ;
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
std::string GetCurrentTime(void)
{
    time_t now = clock();
    return std::to_string(now);
}
 
 
void MoveCommand::Execute()
{
    ConstraintSolver* solver = ConstraintSolver::Instance();
    _state = solver->CreateMemento();
    _target->Move(_delta);
    solver->Solve();
}
 
void MoveCommand::Unexecute()
{
    ConstraintSolver* solver = ConstraintSolver::Instance();
    _target->Move(-_delta);
    solver->SetMementoAndSolve(_state);
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
void main()
{
    Graphic r1, r2;
    ConstraintSolver::Instance()->SetConstraint(&r1, &r2);
    std::cout << ConstraintSolver::Instance()->GetConstraint() << std::endl;
 
    MoveCommand* a = new MoveCommand(&r1, 10);
    a->Execute();
    std::cout << ConstraintSolver::Instance()->GetConstraint() << std::endl;
 
    a->Unexecute();
    std::cout << ConstraintSolver::Instance()->GetConstraint() << std::endl;
}
cs


Graphic 은 움직이는데 MoveCommand 객체를 통해 움직인다.

그리고 Graphic의 전체적인 움직임을 제한하고 통제하는 것이 ConstraintSolver 이다.

또한 이것이 이번의 Originator 가 된다.


ConstraintSolver 의 특징을 잘 기억해야지 위의 예제의 이해가 쉬워진다.

이건 전역적으로 전체적인 흐름을 제어하며, 

Graphic 이 변경된 후 한번 Solve() 를 호출 겹친다거나 화면을 넘는다던가의 제약을 해결해줘야 한다. 


이때 주의해야할 점은 ConstraintSolver 의 상태는 단순히 Command 의 실행취소로는 복구가 불가능하다는 것이다.

여기서는 현재시간을 GetTime() 라는 함수로 넣어서 이러한 성질을 보였다.  


우리가 이번에 Memento 를 실행하는 이유는 이러한 성질을 저장 및 복구하기 위해서이다.


main 을 보면 알겠지만 처음 SetConstraint 를 함으로서 통제할 Graphic 들을 추적한다.

그리고 MoveCommand 로 이동하게 된다.


MoveCommand 는 이동하기 전에 기존의 ConstraintSolver 의 상태를 저장하고, Graphic 을 이동한 후 ConstraintSolver 의 Solve 를 호출한다.

그런데 실행취소를 하려면 과거의 상태가 필요하다.

그래서 전에 저장한 State 를 가지고 Solve 를 한다.

아래처럼.




추가 설명

 
위 예시는 그렇지 않지만, Memento 를 Command 기법과 함께 사용해서 객체를 저장하는 내용을 Command 들의 집합으로 만들어냄.


댓글 없음:

댓글 쓰기

List