Design pattern/구조 패턴

[디자인 패턴] 12장 플라이웨이트 패턴

미스터로즈 2021. 5. 14. 21:07

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

 

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

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

 

플라이웨이트 패턴은 객체를 공유하기 위해 구조를 변경하는 패턴입니다. 객체를 공유하면 객체를 재사용할 수 있어 시스템 자원이 절약됩니다.

 

메모리 자원

$obj = new 클래스 명;

위와 같이 new 키워드를 통해서 객체를 생성합니다. 이때 시스템 자원을 할당받게 됩니다.

 

이러한 객체는 동작을 수행하는 메서드와 데이터를 포함하는 프로퍼티로 구성돼 있습니다.

 

클래스를 설계할 때는 객체가 하나의 책임만 갖도록 합니다. 이를 단일 책임 원칙이라고 합니다.

하나의 객체가 다양한 기능과 책임을 가지면 관리하기가 어려워집니다.

 

재사용할 수 있는 유사한 동작들은 별개의 클래스로 분리하여 상속으로 결합합니다.

 

분리하기 전 코드

<?php

class Korean
{
    public function hello()
    {
        return "안녕하세요 \n";
    }
}

class English
{
    public function hello()
    {
        return "hello \n";
    } 
}

$ko = new Korean;
echo $ko->hello();

$en = new English;
echo $en->hello();

유사 기능으로 분리한 코드

<?php

class Hello
{
    public function console($msg)
    {
        return $msg."\n";
    }

    public function browser($msg)
    {
        return $msg."<br/>";
    }
}

class Korean extends Hello
{
    public function hello()
    {
        return $this->console("안녕하세요");
    }
}

class English extends Hello
{
    public function hello()
    {
        return $this->console("hello");
    } 
}

$ko = new Korean;
echo $ko->hello();

$en = new English;
echo $en->hello();

이와 같이 객체를 세분화 하면 및 분리하면 유지 보수와 확장이 용이합니다. 하지만, 잘게 분리된 객체가 많을 경우 관리하기가 쉽지 않으며, 객체 재사용 시 중복이 발생할 수도 있습니다.

 

플라이웨이트는 가볍다와 무게의 합성어 입니다. 즉, 객체지향에서 가벼운 객체란 메모리를 적게 사용하는 객체를 말합니다. 

중복된 객체를 개별적으로 상속하거나 생성하지 않고 자원을 재사용함으로써 효율을 개선합니다.

 

자원 공유

플라이웨이트 패턴은 객체를 공유하기 위한 패턴입니다. 즉, 생성된 객체를 공유하여 재사용하는 방법을 제시합니다.

 

객체를 생성하는 것보다 의존성 주입을 통해서 자원의 낭비를 막을 수 있습니다.

<?php

class Hello
{
    public function console($msg)
    {
        return $msg."\n";
    }

    public function browser($msg)
    {
        return $msg."<br/>";
    }
}

class Korean
{
    private $hello;
    public function __construct($hello)
    {
        // 의존성 주입
        $this->hello = $hello;
    }

    public function hello()
    {
        return $this->hello->browser("안녕하세요");
    }
}

class English
{
    private $hello;
    public function __construct($hello)
    {
        // 의존성 주입
        $this->hello = $hello;
    }

    public function hello()
    {
        return $this->hello->browser("hello");
    } 
}

// Hello 객체 중복생성
$ko = new Korean(new Hello);
echo $ko->hello();

$en = new English(new Hello);
echo $en->hello();

echo "\n메모리 사용량=".memory_get_usage();

Korean 클래스와 English 의 construct의 몸체를 보면 의존성 주입 코드를 확인하실 수 있습니다. 위 코드는 의존 객체를 중복 생성합니다.

 

<?php

class Hello
{
    public function console($msg)
    {
        return $msg."\n";
    }

    public function browser($msg)
    {
        return $msg."<br/>";
    }
}

class Korean
{
    private $hello;
    public function __construct($hello)
    {
        // 의존성 주입
        $this->hello = $hello;
    }

    public function hello()
    {
        return $this->hello->browser("안녕하세요");
    }
}

class English
{
    private $hello;
    public function __construct($hello)
    {
        // 의존성 주입
        $this->hello = $hello;
    }

    public function hello()
    {
        return $this->hello->browser("hello");
    } 
}

// 객체할당1
$hello = new Hello;

// 객체할당2
$ko = new Korean($hello);
echo $ko->hello();

// 객체할당3
$en = new English($hello);
echo $en->hello();

echo "\n메모리 사용량=".memory_get_usage();

위 코드는 의존 객체를 공유하는 코드입니다. 이 코드를 그림으로 이해하면,

이전 팩토리 패턴에서 학습한 것처럼 직접 객체를 생성하면 효율적으로 관리할 수 없습니다...

따라서 객체 생성을 대신 처리하는 Factory 클래스를 만들어 사용하는 것이 좋습니다.

 

객체를 공유하려면 동일한 객체를 생성해야 합니다. 따라서 싱글턴 패턴과 결합하여 사용하면 됩니다.

 

<?php

class Hello
{
    private static $Instance;
    public static function instance()
    {
        // 객체의 생성을 처리
        if (isset(self::$Instance)) {
            echo "기존 객체를 반환합니다.\n";
            return self::$Instance;
        } else {
            echo "공유 객체를 생성합니다.\n";
            self::$Instance = new self();
            return self::$Instance;
        }
    }

    public function console($msg)
    {
        return $msg."\n";
    }

    public function browser($msg)
    {
        return $msg."<br/>";
    }
}


class Factory
{
    public function make()
    {
        // 객체의 생성을 처리
        echo "팩토리 생성요정=";
        return Hello::instance();
    }
}

// 팩토리 객체1
$hello1 = (new Factory())->make();

// 팩토리 객체1
$hello2 = (new Factory())->make();

if ($hello1 === $hello2) {
    echo "동일한 객체입니다.\n";
} else {
    echo "서로다른 객체입니다.\n";
}

위와 같이 팩토리 패턴과 싱글턴 패턴을 같이 결합하여 공유 객체를 만듭니다.

 

플라이웨이트 패턴은 보다 효율적인 공유 객체를 관리하기 위해 별도의 저장소를 갖고 있는데 이를 공유 저장소라고 합니다.

<?php

class Hello
{
    private static $Instance;
    public static function instance()
    {
        // 객체의 생성을 처리
        if (isset(self::$Instance)) {
            echo "기존 객체를 반환합니다.\n";
            return self::$Instance;
        } else {
            echo "공유 객체를 생성합니다.\n";
            self::$Instance = new self();
            return self::$Instance;
        }
    }

    public function console($msg)
    {
        return $msg."\n";
    }

    public function browser($msg)
    {
        return $msg."<br/>";
    }
}


class Factory
{
    private $pool=[];

    public function make($name)
    {
        if(!isset($this->pool[$name]) ) {
            echo "팩토리 생성요정=";
            $this->pool[$name] = $name::instance();
            return $this->pool[$name];
        }

        echo "저장된 pool 객체 반환\n";
        return $this->pool[$name];

        
    }
}

// 팩토리 객체1
$factory = new Factory();
$hello1 = $factory->make("Hello");

// 팩토리 객체1
$hello2 = $factory->make("Hello");

if ($hello1 === $hello2) {
    echo "동일한 객체입니다.\n";
} else {
    echo "서로다른 객체입니다.\n";
}

factory클래스에 보면 $pool이라는 공유 공간에 저장합니다.

 

 

플라이웨이트 패턴은 무분별하게 객체를 생성하지 않고 기존 객체를 참조함으로써 중복을 방지합니다. 이러한 공유 객체를 저장하는 방식을 인스턴스 풀, 레지스트리 패턴이라고도 부릅니다.

 

상태 구분

플라이웨이트 패턴에서 객체를 공유합니다. 객체 공유는 본질적 공유와 부가적 공유로 구분합니다.

 

공유되는 객체의 데이터가 변경되면 참조되는 모든 다른 객체에도 영향을 미치는데 이를 사이드 이펙트라고 합니다.

 

사이드 이펙트 현상없이 안정적으로 객체를 공유하려면 어떤 변경도 없이 객체를 있는 그대로 참조해서 사용해야 합니다. 이러한 상태를 본질적 상태라고 합니다.

 

이와 달리 부가적 상태는 객체를 공유할 때, 상태값에 따라 달라지는 것을 의미합니다.

이와 같은 경우는 객체의 특정 데이터 값을 변경해 참조하는 다른 객체에 영향을 주기 위해서입니다.

 

나비효과라고 불려지는 사이드 이펙트는 소프트웨어 개발 시, 매우 주의해야 하는 문제입니다.

공유되는 객체는 사이드 이펙트 문제에 노출될 확룰이 매우 높습니다.

따라서 공유 객체를 여러 곳에서 참조할 때는 신중하게 판단한 후 적용해야 합니다.

 

대량의 객체를 생성 및 관리할 경우 플라이웨이트 패턴은 매우 유용한 대안이 될 수 있습니다.