본문 바로가기

IT 일기

데코레이터 패턴이란? (Go)

728x90

디자인 패턴중 하나인 Decorator pattern에 대해 알아보고자 한다. 그전에 우선 디자인 패턴이 무엇이고 왜 나왔는지를 얘기해보자.

 

OOP가 대중화 되면서 Object-oriented한 코드를 짜기 위한 원칙들인 SOLID와, 원칙을 구현하기 위한 도구들인 상속, 추상화, 캡슐화, 다형성 등등이 생겨났다. 시간이 지남에 따라 특정 상황마다 특정 구조의 설계 스타일이 적용되기 시작했고 이를 하나의 패턴으로 만들면서 여러가지 패턴들이 등장했다.

 

Decorator pattern도 이런 과정을 거쳐 등장하지 않았을까 싶다.

 

"In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class."

 

DP(Decorator Pattern)의 정의가 이렇다고 한다. 간단하게 설명하자면 "한 클래스 내에서 다른 객체에 영향을 주지 않고 특정 객체에 기능을 추가하게 해주는 설계" 이다. 즉, 하나의 클래스 내에 존재하는 여러 객체간의 Dependency를 끊어주는 설계이다. Dependency를 끊어내며 SRP와 OCP를 지향할 수 있게 된다. SRP,OCP 등 설명

 

Understand SOLID in Go

https://en.wikipedia.org/wiki/SOLID SOLID - Wikipedia From Wikipedia, the free encyclopedia Object-oriented software engineering design principles This article is about the SOLID principles of object-oriented programming. For the fundamental state of matte

hobo1229.tistory.com

 

 

이걸 Diagram으로 보면 이런 모습이다

class Diagram & Sequence Diagram

시퀀스 다이어그램 기준으로 설명하자면, Client가 operation을 수행하면 Decorator1 -> Decoratr2 -> Component 순서로 호출이 되고 함수 동작은 이 반대의 순서로 돌아간다. 여기서 중요한건 Component라고 하는 기능이 수행되고 그 다음에 순차적으로 Decorator 들이 불리며 Component 기능에 다른 기능을 추가하며 최종 결과를 Client에게 전달해준다는 것이다. ( Component == 핵심기능, Decorator들 == 부가기능들)

 

이제 Go언어를 통해 직접 코드를 쳐보며 위 패턴을 이해해보자~

 

Simple Example with Go

이 단계를 거치는 코드를 작성해 볼것임. 바로 코드를 보면

 

package main

import (
	"fmt"

	"github.com/tuckersGo/goWeb/web9/cipher"
	"github.com/tuckersGo/goWeb/web9/lzw"
)

type Component interface {
	Operator(string)
}

var sentData string
var receiveData string

type SendComponent struct{}

func (self *SendComponent) Operator(data string) {
	// Send data
	sentData = data
}

type ZipComponent struct {
	com Component
}

func (self *ZipComponent) Operator(data string) {
	zipData, err := lzw.Write([]byte(data)) //데이터 압축하기
	if err != nil {
		panic(err)
	}
	self.com.Operator(string(zipData)) //압축한 데이터로 호출 (이래서 필요하구나)
}

type EncryptComponent struct {
	key string
	com Component
}

func (self *EncryptComponent) Operator(data string) {
	// Send data

	encryptData, err := cipher.Encrypt([]byte(data), self.key) //암호화
	if err != nil {
		panic(err)
	}
	self.com.Operator(string(encryptData))
}

type DecryptComponent struct {
	key string
	com Component
}

func (self *DecryptComponent) Operator(data string) {
	// Send data

	decryptData, err := cipher.Decrypt([]byte(data), self.key) //복호화
	if err != nil {
		panic(err)
	}
	self.com.Operator(string(decryptData))
}

type UnzipComponent struct {
	com Component
}

func (self *UnzipComponent) Operator(data string) {
	unzipData, err := lzw.Read([]byte(data)) //데이터 압축하기
	if err != nil {
		panic(err)
	}
	self.com.Operator(string(unzipData)) //압축한 데이터로 호출 (이래서 필요하구나)
}

type ReadComponent struct{}

func (self *ReadComponent) Operator(data string) {
	receiveData = data
}

func main() {
	sender := &EncryptComponent{key: "asdfe",
		com: &ZipComponent{
			com: &SendComponent{}}} //닫는 중괄호는 붙여줘야함. 혹은 , 찍어주기

	sender.Operator("Hello World")
	fmt.Println(sentData)

	receiver := &UnzipComponent{
		com: &DecryptComponent{key: "asdfe",
			com: &ReadComponent{}}}
	receiver.Operator(sentData)
	fmt.Println(receiveData)

	// 코드의 수정이 매우 간편해짐.
}

이와 같음.

main 함수 부터 확인해보자~ 

sender라고 하는 변수는 Component 인터페이스를 implement하는 EncryptComponent의 객체이다. 또 EncryptComponent의 property로 암호에 필요한 key와 또 다른 Component를 implement하는 ZipComponent가 들어오고 ZipComponent의 property로는 또 SendComponent라고 하는 Component를 implement하는 객체가 들어온다.

 

이를 그림으로 표현하면

호엥엥

이렇게 된다. Sender라고 하는 구조체 안의 객체들이 서로 영향을 받지 않도록 나눠준 것이다!

그 다음 명령어인 sender.Operator("Hello-world")를 수행한다면 EncryptComponent의 Operator가 수행되고 이를 코드로 확인해보면 암호화 이후 self.com.Operator()를 호출한다. 여기서 self.com은 EncryptComponent의 property로 초기화된 ZipComponent이다.

즉, sender.Operator == SendComponent.Operator(ZipComponent.Operator(EncryptComponent.Operator())) 와 같이 동작하게 된다.

 

호옹이... 이를 통해 얻는 장점이 뭐지...? 하고 애매해질 수 있는데 좀 전에도 말했듯이 하나의 구조체 안의 객체들이 서로 영향을 받지 않는다! (인자는 주고 받지~)

=> 각 component에서 수행해야 할 내용들이 어떻게 구성되는지 관계 없이 정해진 인자만 들어온다면 상관이 없다는 것 == OCP를 만족함

 

그리고 만약 중간에 새로운 기능이 추가된다면 마찬가지로 interface를 implement하는 Decorator 패턴을 추가해주고 적절한 위치에 껴서 변수로 만들어주기만 하면 기능 확장성도 굉장히 좋다!

당연히 기존 코드 수정도 Decorator에서 구현하는 함수만 수정하면 되기 때문에 편하다~

728x90

'IT 일기' 카테고리의 다른 글