Design pattern/행동 패턴

[디자인 패턴]21장 메멘토 패턴

미스터로즈 2021. 6. 16. 19:46

공부하기 위해서 요약정리해놓은 것입니다..

 

정확하고 꼼꼼한 자료는 쉽게 배워 바로 써먹는 디자인 패턴을 확인하시고

코드는 github.com/infohojin/patterns 을 참고해 주세요.

 

메멘토 패턴은 객체의 상태를 저장하여 이전 상태로 복구하는 패턴입니다.

 

상태 저장

객체는 고유한 상태를 갖고 있으며 객체의 상태는 프로그램 실행 중에 다른 객체에 의해 끊임없이 값이 변경됩니다.

 

객체는 프로퍼티와 메서드로 구성되고 프로퍼티는 객체의 상태 형태로 값을 가집니다. 그리고 메서드는 객체의 행위로 내부 상태를 변경하고, 상태값에 따라 동작을 수행합니다.

 

<?php

class Hello
{
    private $message;

    public function __construct($msg)
    {
        $this->message = $msg;
    }

    public function setMessage($msg)
    {
        $this->message = $msg;
    }

    public function getMessage()
    {
        return $this->message;
    }
}

인사말을 출력하는 Hello 클래스는 하나의 상태 값을 갖고 있습니다. 객체 내에 선언된 프로퍼티에 인사말을 저장하고 읽어봅니다.

 

<?php

require "hello.php";

// 첫번째 인사말
$obj = new Hello("안녕하세요.");
echo $obj->getMessage()."\n";

// 상태변경
// 두번째 인사말
$obj->setMessage("Hello nice meet you.");
echo $obj->getMessage()."\n";

Hello 클래스의 객체를 생성합니다. 객체의 초기 인사말은 객체 생성 과정에서 설정하고, 설정된 인사말은 getMessage() 메서드를 이용해 상태값을 출력합니다.

 

객체의 상태값과 설정 방법

객체의 행위는 상태값을 읽고 변경을 수행합니다.

 

캡슐화

객체는 데이터와 행위를 캡슐화합니다. 캡슐환는 객체지향의 고유 특징입니다.

 

객체들은 상호 밀접한 의존 관계를 갖고 있고, 객체는 의존성 객체로 메시지를 전송하며 행위를 호출해 동작을 수행합니다.

 

다른 객체와 의존 관계인 복합 객체를 저장하거나 복원하는 것은 쉽지 않은 작업입니다. 단순히 하나의 객체만 복원하는 것이 아니라 복원 객체와 의존 관계인 모든 객체를 함께 이전 상태로 복원해야 합니다.

 

객체 지향에는 3가지 종류의 접근 제어 속성이 있습니다.

- Public

- Private

- Protected

 

public 속성은 객체 내부에 누구나 접근할 수 있습니다. 하지만 private과 protected는 제한된 접근만 허락합니다. 만일 객체의 상태값이 private이나 protected라면 복원에 필요한 상태를 읽을 수 없습니다.

 

객체를 복원하기 위해서는 완전한 객체의 내부 접근이 필요합니다. 완전한 객체의 내부 접근을 허용하는 방법은 모두 public 속성으로 설정합니다.

하지만 public으로 해서 노출하면 객체를 캡슐화 정책을 위반하게 됩니다. 기능적 보완을 위해 약간의 절충작업이 필요합니다.

 

메멘토 패턴은 객체 상태를 다른 객체에 저장했다가 다시 복원합니다. 객체를 복원할 때 캡슐화 정책에 영향을 주지 않으면서도 안전하게 복원하는 절충안이 필요합니다.

메멘토 패턴은 캡슐화 위반을 최소화하면서 객체 저장과 복원을 실행할 수 있도록 돕는 패턴입니다.

저장과 복원 작업을 처리하는 중간 매개체를 이용하면 보다 쉽게 상태 이력을 관리할 수 있습니다.

 

메멘토

메멘토는 SolverState로 객체의 상태를 관리합니다. 객체의 상태를 저장하고, 저장된 상태의 객체를 복원합니다.

 

동작 전의 상태로 객체를 되돌리는 방법은 2가지입니다. 하나는 객체를 실행하기 전에 동작을 역순으로 처리하는 로직을 다시 작성하는 방법이고, 또 하나는 객체의 동작을 되돌리기 위해 실행 전의 객체를 통째로 저장하는 방법입니다.

 

복잡한 복원 로직을 작성하는 방법보다는 객체를 통째로 저장하는 것이 수월합니다. 하지만 객체를 임시 저장하는 것도 복원 시 캡슐화 정책과 충돌하므로 간단하지만은 않습니다.

 

객체 복원은 객체를 이전 상태로 되돌리는 것을 말합니다. 자신의 상태값을 가진 객체를 저장했다가 이전 상태로 되돌릴 때 이력을 참조하여 상태 값을 변경합니다.

 

메멘토 패턴은 캡슐화를 파괴하지 않고 객체 상태를 저장하는 방법을 제안합니다. 메멘토는 객체를 스냅숏 형태로 저장합니다.

 

메멘토는 객체 저장과 복원을 위해 2가지 인터페이스를 사용하며 인터페이스를 이용해 관리 방법을 구분합니다.

 

- 원조본

- 케어테이커

 

2가지 구현의 차이점은 메멘토에 얼마나 많은 접근 권한을 허용하는가의 차이입니다. 원조본은 광범위한 메멘토의 접근을 모두 허용하지만 케어테이커는 제한된 범위 안에서 허용합니다.

 

메멘토 패턴을 구현하기 위해 Memento 클래스를 설계합니다. 객체의 정보를 저장하는 프로퍼티와 저장된 객체에 접근하기 위한 메서드로 구성되어 있습니다.

 

<?php
/**
 * 메멘토
 */
class Memento
{
    // 객체를 저장합니다.
    protected $obj;

    /**
     * 원조본(Originator)에 의해서 생성됩니다.
     */
    public function __construct($obj)
    {
        // 객체를 저장합니다.
        $this->obj = clone $obj;
    }

    /**
     * 저장된 객체를 읽어 옵니다.
     */
    public function getObject()
    {
        return $this->obj;
    }
}

생성 인자로 전달된 객체를 내부 프로퍼티에 복제합니다.

 

Originator 클래스

원조본은 실체 객체와 메멘토 사이의 중간 매개체 역활을 수행합니다.

 

메멘토 패턴에서는 객체를 저장하기 위해 직접 메멘토 객체에 접근하지 않으며 객체를 저장, 복원하기 위해 중간 매개체인 Originator 클래스를 생성합니다.

<?php
/**
 * 상태를 가지고 있는 객체입니다.
 */
class Originator 
{
    // 상태를 저장하기 위해서 변수를 하나 가지고 있습니다.
    public $state;

    /**
     * 메멘토
     */
    // 메멘토의 객체를 생성하여 반환을 합니다.
    public function create()
    {
        echo ">메멘토 객체를 생성합니다.\n";
        return new Memento($this->state);
    }

    // 복원합니다.
    public function restore($memento)
    {
        echo ">메멘토 객체로 복원합니다.\n";
        $this->state = clone $memento->getObject();
    }

    /**
     * 상태
     */
    // 상태를 읽어옵니다.
    public function getState()
    {
        return $this->state;
    }

    // 상태를 설정합니다.
    public function setState($state)
    {
        $this->state = $state;
    }
}

캡슐화는 외부의 접근을 제어함으로써 악의적인 변경을 방지합니다. 메멘토는 제한적인 접근만 허용하며 캡슐화를 위반하지 않습니다.

이제 메멘토 객체로의 접근은 Originator만 가능합니다.

 

CareTaker 클래스

케어테이커는 실행 취소 메커니즘이고 제한적 범위의 인터페이스를 가집니다.

 

케어테이커는 다수의 메멘토를 보관하고 관리합니다. 또한 CareTaker 클래스와 Memento 클래스는 느슨한 구조로 연결돼 있습니다.

<?php

class CareTaker
{
    protected $stack;

    /**
     * 케어테이커 생성자
     */
    public function __construct()
    {
        // 스텍을 초기화 합니다.
        $this->stack = array();
    }

    /**
     * 스텍에 저장을 합니다.
     */
    public function push($origin)
    {
        // 원조본을 이용하여 메멘토의 인스턴스를 생성합니다.
        $memento = $origin->create();

        // 메멘토를 스택에 저장합니다.
        array_push($this->stack, $memento);
    }

    /**
     * 스텍에서 객체를 읽어 옵니다.
     */
    public function undo($origin)
    {
        // 스택에서 메멘토를 읽어 옵니다.
        $memento = array_pop($this->stack);
        
        // 멘멘토를 이용하여 원조본을 복원합니다.
        $origin->restore($memento);

        // 복원된 객체를 반환합니다.
        return $origin->getState();
    }
}

CareTaker 클래스는 Memento 객체를 관리합니다. 케어테이커는 메멘토 객체를 스택 구조로 저장하며 복원시 스택에서 메멘토를 가져옵니다.

 

케어테이커 스택에 저장된 객체를 하나씩 읽어서 다시 실행해봅시다. 실행했던 순서의 역순으로 객체가 복원되는 것을 확인할 수 있습니다.