Design pattern/행동 패턴

[디자인 패턴] 16장 방문자 패턴

미스터로즈 2021. 5. 21. 19:45

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

 

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

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

 

방문자 패턴은 공통된 객체의 데이터 구조와 처리를 분리하는 패턴입니다.

 

데이터 처리

객체는 데이터와 행위가 있으며 객체의 행위는 데이터를 처리합니다.

 

객체는 데이터와 함수를 하나의 그룹으로 묶어 처리하는데, 이러한 객체의 특성을 캡슐화라고 하며 다른 말로는 번들링이라고도 합니다.

 

캡슐화는 C 언어에서 구조체나 공용체로 데이터만 묶어 처리했습니다.

그러나 구조체와 달리 객체는 함수도 포함합니다.

 

최근의 캡슐화는 데이터와 행위를 위한 메서드 함수를 하나의 객체로 묶어 처리합니다. 캡슐화는 데이터와 행위를 하나의 객체로 만들어 재사용을 늘립니다.

 

외부로부터 은닉화된 데이터에 접근하려면 세터, 게터와 같은 데이터 접근 메서드를 함께 구현해야 합니다. 이러한 메서드는 데이터 캡슐화 처리 시 같이 설계합니다.

<?php

class Product
{
    protected $name;
    protected $price;
    protected $num;

    // Setter: 상품명 설정
    public function setName($name)
    {
        $this->name = $name;
    }

    // Getter : 상품명 확인
    public function getName()
    {
        return $this->name;
    }

    // Setter: 가격 설정
    public function setPrice($price)
    {
        $this->price = $price;
    }

    // Getter : 가격 확인
    public function getPrice()
    {
        return $this->price;
    }

    // Setter: 수량 설정
    public function setNum($num)
    {
        $this->num = $num;
    }

    // Getter : 수량 확인
    public function getNum()
    {
        return $this->num;
    }

    // 행위 추가동작
    public function getTax($tax=10)
    {
        return ( $this->price * $this->num ) * $tax/100;
    }

}

 

캡슐화 된 객체의 메서드는 단순히 데이터에만 접근할 수 있는 것이 아니라 필요시 데이터 가공도 같이 처리합니다.

 

캡슐화된 객체에 데이터를 처리하기 위한 동작을 추가하고 싶은 경우도 있을 것입니다. 일반적으로 객체는 데이터를 처리하기 위한 다수의 행위들을 갖고 있습니다.

 

일반적으로 데이터를 갖고 있는 클래스에 행위 메서드를 삽입합니다.

데이터를 처리하는 행위가 객체에 추가되면 클래스 선언을 수정해야 합니다. 데이터를 처리해야 하는 작업이 늘어날수록 코드 수정이 잦아집니다.

 

객체의 행위의 대부분은 객체 내 데이터를 중심으로 동작을 처리합니다. 하지만 객체의 행위가 다른 객체의 정보를 참조하는 경우도 있습니다.

 

여러 객체에 데이터가 분산된 경우 객체 간 관계가 복잡합니다. 따라서 데이터가 여러 객체로 분산된 경우,객체의 데이터에 접근할 수 있는 구조의 큰 객체가 필요합니다. 큰 객체는 데이터가 포함된 객체를 갖고 있는 복합 객체입니다.

 

복합 객체 생성은 시스템 자원을 소모하며 복잡한 결합 단계가 필요합니다. 하지만 방문자 패턴은 분산된 객체의 데이터와 행위를 순차적으로 접근하여 데이터를 처리할 수 있도록 합니다.

 

분리

방문자 패턴은 분산된 객체에서 공통된 처리 로직만 분리합니다. 그리고 공통된 로직 구조로 별도의 객체로 분리합니다.

 

객체는 데이터와 이를 처리하기 위한 행위를 포함하고 있습니다.

<?php

class Cart extends Product
{
    public function __construct($name, $price, $num=1)
    {
        $this->name = $name;
        $this->price = $price;
        $this->num = $num;
    }

    public function getTax($tax=10)
    {
        return ( $this->price * $this->num ) * $tax/100;
    }

    public function list()
    {
        $order = $this->name;
        $order .= ", 수량=".$this->num;
        $order .= ", 가격=".$this->price * $this->num." 입니다.\n";
        return $order;
    }

}

 

분리된 객체는 상송을 통해 확장합니다.

 

데이터를 포함한 객체에서 행위만 별도의 객체로 분리하면 데이터를 갖고 있는 객체는 크게 수정하지 않고도 행위를 쉽게 변경할 수 있습니다. 객체의 데이터와 행위를 분리함으로써 보다 나은 확장성을 갖게됩니다.

 

원소 객체

객체의 데이터와 행위가 다수의 객체로 분산된 경우 방문자 패턴을 활용합니다. 방문자 패턴을 통해 분산된 데이터를 처리하고, 공통된 로직을 부리하여 변경을 쉽게 처리합니다. 방문자 패턴으로 분리된 객체는 데이터와 연산을 쉽게 처리합니다.

 

원소 객체는 데이터를 보관하는 구조 클래스입니다.

 

방문자 패턴의 원소 객체는 외부로부터 자신의 데이터에 접근할 수 있는 수동적 방식을 사용합니다.

 

모든 원소에 구현해야 하는 메서드를 인터페이스로 적용했습니다. 인터페이스를 적용하여 실제 객체를 선언합니다.

 

<?php

class Cart extends Product implements Visitable 
{
    public function __construct($name, $price, $num=1)
    {
        $this->name = $name;
        $this->price = $price;
        $this->num = $num;
    }

    // 인터페이스 구현
    public function accept($visitor)
    {
        // 방문자의 주문을 호출합니다.
        // 인자로 원소 객체 자신을 전달합니다.
        return $visitor->order($this);
    }

    public function getTax($tax=10)
    {
        return ( $this->price * $this->num ) * $tax/100;
    }

    public function list()
    {
        $order = $this->name;
        $order .= ", 수량=".$this->num;
        $order .= ", 가격=".$this->price * $this->num." 입니다.\n";
        return $order;
    }

}

선언된 클래스에는 인터페이스인 accept() 메서드를 같이 구현해야 합니다.

 

방문자 패턴은 실제 처리 로직을 다른 객체로 분리하여 위임합니다.

 

모든 원소 객체는 위임을 위해 인터페이스에 선언된 accept() 메서드를 구현하며, accept() 메서드는 매개변수를 통해 위임되는 객체를 전달받습니다.

 

객체는 캡슐화를 통해 데이터와 행위를 은닉할 수 있습니다. 하지만 방문자 패턴은 방문하는 외부 객체에 자신의 모든 데이터와 행위의 접근을 허용합니다.

즉, 캡슐화와 데이터 은닉을 활용할 수 없게 방해하는 요인입니다.

 

방문자

Visitor 방문자를 의미합니다. 원소 객체의 accept는 외부의 Visitor를 전달 받도록 설계합니다.

 

Visitor 인터페이스

<?php
/**
 * 방문자
 */
interface Visitor
{
    public function order($visitable);
}

 

구체적 Visitor 객체 생성

<?php
/**
 * 방문 조사
 */
class Visitant implements Visitor
{
    // 상태값
    private $total;
    private $num;

    public function __construct()
    {
        echo "주문을 처리합니다.\n";
        $this->total = 0;
        $this->num = 0;
    }

    // 원소 객체를 전달 받습니다.
    public function order($visitable)
    {
        echo "==상품내역==\n";
        
        // 방문자를 확인을 합니다.
        if ($visitable instanceof Cart) {
            $msg = "상품명=".$visitable->getName();

            $msg .= ", 수량=".$visitable->getNum();
            
            $total = $visitable->getPrice() * $visitable->getNum();
            $msg .= ", 가격=".$total." 입니다.\n";

            $this->total += $total;
            $msg .= "합계=".$this->total;
            
            // 주문건수 증가
            $this->num++;

            return $msg;
        }
    }

    public function getTotal()
    {
        return $this->total;
    }

    public function getNum()
    {
        return $this->num;
    }
}

 

반복자

방문해서 처리하는 원소 객체가 여러 개일 경우 반복자 패턴을 결합해 사용합니다.

 

방문자 패턴은 데이터와 처리 행위를 분리하는데, 공통된 로직을 별도의 객체로 분리함으로써 행위를 보다 쉽게 추가할 수 있습니다.

 

<?php

include "visitable.php";

include "product.php";
include "cart.php";

include "visitor.php";
include "visitant.php";


echo "쇼핑몰 상품주문 처리\n";
echo "-----\n";

// 공통된 방문자 객체
$visitant = new Visitant;

// 방문객이 방문지를 하나씩 
$list = [
    new Cart("컵라면", 900, 3),
    new Cart("아이스크림", 1500, 1),
    new Cart("음료수", 2800, 1)
];
foreach ($list as $obj) echo $obj->accept($visitant);

echo "\n-----\n";
echo "감사합니다. 주문건수 =".$visitant->getNum()."\n";
echo "주문합계 =". $visitant->getTotal() ." 입니다.";

먼저 공통된 방문자 객체를 하나 생성합니다. 방문자 객체는 원소 객체의 매개변수로 전달됩니다.