설명
객체의 상태의 스냅샷을 저장하기 위한 패턴.
객체의 상태를 하드코딩을 해서 저장하고 복구시키면 캡슐화 원칙이 깨짐.
그래서 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 들의 집합으로 만들어냄.
댓글 없음:
댓글 쓰기