본문 바로가기
공부/Object-Oriented Design Pattern

Memento Pattern (메멘토 패턴)이란

by 혼밥맨 2021. 5. 28.
반응형

Memento Pattern (메멘토 패턴)이란

 

 

의도

Memento Pattern는 개체의 구현 세부 정보를 표시하지 않고 개체의 이전 상태를 저장하고 복원할 수 있는 동작 설계 패턴입니다.

 

문제점

텍스트 편집기 앱을 만든다고 가정해 보십시오. 편집기는 간단한 텍스트 편집 외에도 텍스트 형식 지정, 인라인 이미지 삽입 등을 수행할 수 있습니다.

어느 시점에서 사용자가 텍스트에서 수행된 작업을 실행 취소하도록 허용하기로 결정했습니다. 이 기능은 세월이 흐르면서 너무 흔해져서 요즘 사람들은 모든 앱이 그것을 가질 것으로 기대하고 있습니다. 구현을 위해 직접적 접근 방식을 선택했습니다. 작업을 수행하기 전에 앱은 모든 개체의 상태를 기록하고 일부 저장소에 저장합니다. 나중에 사용자가 작업을 되돌리기로 결정하면 앱은 기록에서 최신 스냅샷을 가져와 이를 사용하여 모든 개체의 상태를 복원합니다.

 

그 상태 스냅샷을 생각해 봅시다. 정확히 어떻게 생산하시겠습니까? 개체의 모든 필드를 살펴보고 해당 값을 저장소에 복사해야 할 수 있습니다. 그러나 오브젝트가 내용에 대한 접근 제한을 상당히 완화하는 경우에만 작동합니다. 불행히도, 대부분의 실제 개체는 다른 개체들이 자신의 내부를 쉽게 엿보게 하지 않으며, 중요한 모든 데이터를 private field에 숨깁니다.

open relations를 선호하고 그들을 public으로 공개하는 것입니다. 이러한 접근 방식은 즉각적인 문제를 해결하고 개체 상태에 대한 스냅샷을 마음대로 생성할 수 있지만, 여전히 몇 가지 심각한 문제가 있습니다. 나중에 일부 편집기 클래스를 리팩터링하거나 일부 필드를 추가하거나 제거할 수 있습니다. 쉽게 들리겠지만, 이렇게 하려면 영향을 받는 개체의 상태를 복사하는 클래스도 변경해야 합니다.

 

 

하지만 더 큰 문제가 있습니다. 편집자 상태의 실제 "스냅샷"을 생각해 봅시다. 어떤 데이터를 포함하고 있습니까? 최소한 실제 텍스트, 커서 좌표, 현재 스크롤 위치 등을 포함해야 합니다. 스냅샷을 만들려면 이러한 값을 수집하여 컨테이너에 넣어야 합니다.

아마 이런 컨테이너 물체는 역사를 나타내는 목록 안에 많이 저장될 것입니다. 따라서 컨테이너는 한 클래스의 개체가 될 수 있습니다. 클래스에는 메서드가 거의 없지만 편집자의 상태를 반영하는 많은 필드가 있습니다. 다른 개체가 스냅샷에서 데이터를 쓰고 읽도록 하려면 해당 필드를 공개해야 합니다. 그것은 private이든 아니든 편집자의 모든 상태를 폭로할 것이다. 다른 클래스는 외부 클래스에 영향을 미치지 않고 개인 필드와 메서드 내에서 발생하는 스냅샷 클래스의 모든 작은 변경 사항에 따라 달라집니다.

막다른 골목에 도달한 것 같습니다. 클래스의 모든 내부 세부 정보를 노출하여 너무 취약하게 만들거나 해당 상태에 대한 액세스를 제한하여 스냅샷을 생성할 수 없습니다. 언도(undo)를 구현할 다른 방법이 있습니까?

 

 

해결책

우리가 방금 경험한 모든 문제들은 캡슐화가 깨져서 발생합니다일부 개체는 예정된 것보다 더 많은 작업을 수행하려고 합니다. 일부 작업을 수행하는 데 필요한 데이터를 수집하기 위해 이러한 개체가 실제 작업을 수행하도록 하는 대신 다른 개체의 private field을 침범합니다.

Memento Pattern은 상태 스냅샷을 생성하는 과정을 해당 상태의 실제 소유자, 즉 원본 개체에게 위임합니다. 따라서, "outside"에서 편집자의 상태를 복사하려는 다른 개체 대신, 편집자 클래스 자체는 자신의 상태에 대한 완전한 접근 권한을 가지고 있기 때문에 스냅샷을 만들 수 있다.

이 패턴은 개체의 상태 복사본을 Memento라고 하는 특별한 개체에 저장하는 것을 제안합니다. Memento의 내용은 Memento를 제작한 오브젝트를 제외한 다른 오브젝트에는 접근할 수 없습니다. 다른 개체는 스냅샷의 Meta Data(생성 시간, 수행된 작업의 이름 등)를 가져올 수 있는 제한된 인터페이스를 사용하여 Memento와 통신해야 하지만 스냅샷에 포함된 원래 개체의 상태는 그렇지 않습니다.

 

이러한 제한 정책을 사용하면 일반적으로 Caretakers이라고 하는 다른 개체 내부에 Memento를 저장할 수 있습니다. Caretakers은 제한된 인터페이스를 통해서만 Memento와 함께 작업하므로, Memento 내부에 저장된 상태를 조작할 수 없습니다. 동시에, originator은 Memento 내의 모든 필드에 액세스하여 원하는 대로 이전 상태를 복원할 수 있습니다.

우리의 텍스트 편집기 예에서, 우리는 Caretaker 역할을 하기 위해 별도의 기록 클래스를 만들 수 있다. 편집자가 작업을 실행할 때마다 caretaker 내부에 저장된 Memento 스택이 늘어납니다. 앱의 UI 내에서 이 스택을 렌더링하여 이전에 수행된 작업의 기록을 사용자에게 표시할 수도 있습니다.

사용자가 실행 취소를 트리거하면 기록에서 스택에서 최신 Memento를 가져와 다시 편집기로 전달하여 롤백을 요청합니다. 편집기는 Memento에 대한 전체 액세스 권한을 가지므로, Memento에서 가져온 값으로 자신의 상태를 변경합니다.

 

 

 

구조

Originator 클래스는 자체 상태의 스냅샷을 생성할 수 있을 뿐만 아니라 필요할 때 스냅샷에서 상태를 복원할 수도 있습니다.

Memento는 원본 상태 스냅샷으로 작동하는 값 개체입니다. Memento는 불변으로 만들고 생성자를 통해 한 번만 데이터를 전달하는 것이 일반적입니다.

Caretaker은 "언제"와 "왜"를 통해 originator의 상태를 캡처할 수 있을 뿐만 아니라 상태를 복원해야 하는 시점도 알고 있습니다.

Caretaker은 한 무더기의 Memento들을 저장함으로써 originator의 기록을 추적할 수 있다. Originator가 역사를 거슬러 올라가야 할 때, caretaker은 가장 위에 있는 Memento를 스택에서 가져와 originator의 복원 방법에 전달합니다.

이 구현에서 Memento Class는 원본 내부에 중첩(nested)됩니다. 이렇게 하면 개인으로 선언된 메모의 필드 및 메서드에 originator가 액세스할 수 있습니다. 반면에, Caretaker은 Memento의 필드와 방법에 대한 접근 권한이 매우 제한적이어서, Memento를 스택에 저장하지만 상태를 변조하지는 않는다.

 

 

적용
개체의 이전 상태를 복원할 수 있도록 개체 상태의 스냅샷을 생성하려면 Memento Pattern을 사용합니다.

Memento Pattern을 사용하면 개인 필드를 포함하여 개체 상태의 전체 복사본을 만들고 개체와 별도로 저장할 수 있습니다. 대부분의 사람들이 "undo" 사용 사례 덕분에 이 패턴을 기억하지만, 트랜잭션을 처리할 때(즉, 오류로 인해 작업을 롤백해야 하는 경우) 필수적입니다.

개체의 필드/getter/setter에 대한 직접 액세스가 캡슐화를 위반하는 경우 Memento Pattern을 사용합니다.

Memento는 개체 스스로 상태의 스냅샷을 생성하도록 합니다. 다른 개체는 스냅샷을 읽을 수 없으므로 원래 개체의 상태 데이터를 안전하고 안전하게 만듭니다.

 

 

구현방법

1. Originator 역할을 수행할 클래스를 결정합니다. 프로그램이 이러한 유형의 하나의 중앙 개체 (central object)를 사용하는지 아니면 여러 개의 작은 개체를 사용하는지 아는 것이 중요합니다.

2.  Memento 클래스를 만듭니다. Originator 클래스 내에서 선언된 필드를 미러링하는 필드 집합을 하나씩 선언합니다.

3. Memento 클래스 immutable으로 만드세요. Memento는 constructor를 통해 데이터를 한 번만 승인해야 합니다. 클래스에 setters가 없어야 합니다.

4. 프로그래밍 언어가 nested class를 지원하는 경우, memento를 originator 내부에 중첩합니다. 그렇지 않은 경우, Memento class에서 빈 인터페이스를 추출하고 다른 모든 개체에서 memento를 참조하도록 합니다. 인터페이스에 일부 메타데이터 작업을 추가할 수 있지만 원본의 상태를 표시하는 것은 없습니다.

5. Add a method for producing mementos to the originator class (메멘토 생성 방법을 originator 클래스에 추가합니다). Originator는 그 상태를 Memento's constructor의 하나 또는 여러 개의 인수를 통해 Memento에 전달해야 한다.

6. 메소드의 반환 형식은 이전 단계에서 추출한 인터페이스여야 합니다(모두 추출한 것으로 가정). 후드 아래에서, Memento-producing method은 Memento class와 직접 작동해야 합니다.

7. 원본의 상태를 클래스에 복원하는 메서드를 추가합니다. 그것은 논거의 의미를 논거로 받아들여야 한다. 이전 단계에서 인터페이스를 추출한 경우 매개 변수의 형식으로 지정합니다. 이 경우, 수신되는 오브젝트를 메모코 클래스로 입력해야 합니다. 왜냐하면, 발신인은 해당 오브젝트에 대한 전체 접근 권한이 필요하기 때문입니다.

8. 그것이 명령 객체, 이력 또는 전혀 다른 것을 나타내든 간에, Caretakers는 Originator에게 새로운 메멘토 요청 시기, 저장 방법 및 특정 메멘토와 함께 발신자를 복원할 시기를 알아야 한다.

9. Caretakers와 Originator 사이의 링크는 memento 클래스로 이동할 수 있습니다. 이 경우 각 memento는 해당 memento를 작성한 originator에 연결해야 합니다. Restoration method 또한 memento class로 이동합니다. 그러나 이는 모두 memanto 클래스가 originator에 중첩되거나 originator class가 해당 상태를 재정의하기 위한 sufficient setters를 제공하는 경우에만 의미가 있습니다.

 

 

장점

1. 캡슐화를 위반하지 않고 개체 상태에 대한 스냅샷을 생성할 수 있습니다.
2. Caretaker이 Originator의 상태 기록을 유지하도록 하여 Originator 코드를 단순화할 수 있습니다.

 

단점

1. 클라이언트가 메멘토를 너무 자주 생성하면 앱에서 RAM을 많이 사용할 수 있습니다.
2. Caretakers은 불필요한 Memento들을 파괴할 수 있도록 Originator의 라이프사이클을 추적해야 한다.
3. PHP, Python, JavaScript와 같은 대부분의 동적 프로그래밍 언어는 Memento 내의 상태가 그대로 유지되도록 보장할 수 없습니다.

반응형

댓글