본문 바로가기

IT/헤드퍼스트 디자인패턴

[컴파운드패턴] 디자인 패턴 합치기

안녕하세요 남갯입니다. 


오늘은 디자인패턴을 여러개 섞어서 문제를 해결하는 컴파운드 패턴에 대해 포스팅 해보려고합니다.



컴파운드 패턴이란?

반복적으로 생길수 있는 일반적인 문제를 해결하기 위한 용도로 두개 이상의 패턴을 결합해서 사용하는것을 뜻합니다.



1. 우선 SImUDuck 오리 시뮬레이터를 개조해보겠습니다.



interface Quakable{
fun quack()
}

오리의 행동을 구현하고


class MallardDuck : Quakable{
override fun quack() = println("Quack")
}

class RedheadDuck : Quakable{
override fun quack() = println("Quack")
}

두가지의 오리를 만들었습니다.


class DuckCall : Quakable{
override fun quack() = println("Kwak")
}
class RubberDuck : Quakable{
override fun quack() = println("Squeak")
}


사냥꾼이 오리를 잡기위해 쓰는 기계와 러버덕을 만들었습니다.


테스트를 해본다면


fun main(){
val mallardDuck : Quackable= MallardDuck()
val readheadDuck : Quackable= RedheadDuck()
val duckCall : Quackable= DuckCall()
val rubberDuck : Quackable= RubberDuck()
simulate(mallardDuck)
simulate(readheadDuck)
simulate(duckCall)
simulate(rubberDuck)
}

fun simulate(duck : Quackable){
duck.quack()
}


이렇게 오리가 소리내는것을 테스트 해봤습니다.


class Goose {
fun honk() = println("Honk")
}


하지만 물을 좋아하는 가금류들은 몰려다니므로 거위도 추가를 했습니다.


** 알아가기

우리는 시뮬레이터를 통해 동작을 확인했었는데 사실 거위나 오리나 날기도하고 헤엄치기도하고 소리도내는데

못 집어넣을 이유는 없지 않을까요? 이럴땐 어떤 패턴을 이용해야 할까요?


이전에 오리를 거위로 만들었던 adapter 패턴을 이용하면 됩니다.


class GooseAdapter(val goose : Goose) : Quackable{
override fun quack() = goose.honk()
}

거위를 위한 어뎁터를 만들게되면

val gooseDuck : Quackable = GooseAdapter(Goose())


정상적으로 동작하는것을 알 수 있습니다.



**알아가기2

꽥학자들이 꽥동작에 매료됐습니다. 이럴때 하나의 오리떼가 있을대 꽥소리의 총 횟수를 구하는 연구를 하려합니다.

어떻게 해결이 가능할까요?

class QuackCounterDecorator(val duck: Quackable) : Quackable {
companion object {
var numberOfQuacks = 0
}

override fun quack() {
duck.quack()
numberOfQuacks++
}
}

꽥하는 횟수를 세는 데코레이션 패턴을 통해 객체를 감싸준다면 Duck 코드는 건드리지 않고 만들 수 있습니다.



val mallardDuck: Quackable = QuackCounterDecorator(MallardDuck())
val readheadDuck: Quackable = QuackCounterDecorator(RedheadDuck())
val duckCall: Quackable = QuackCounterDecorator(DuckCall())
val rubberDuck: Quackable = QuackCounterDecorator(RubberDuck())

QuackCounterDecorator 객체를 통해 감싸주기만 하면 됩니다. 즉  매번 Quack객체를 생성할때마다 감싸면 몇번 소리냈는지 알 수 있습니다.



하지만 이렇게 만든 오리들을 보고 데코레이터로 감싸지 못할경우 소리를 셀 수 없는 문제가 발생했습니다.

오리객체를 생성하는 작업은 한군데에서 몰아서 하는건 어떨까요? 오리를 생성하고 데코레이터로 감싸는 부분을 따로 빼내서 캡슐화 하는거죠

이렇게 된 문제를 어떤 패턴을 활용해서 해결하면 될까요?



팩토리패턴을 통해 해결이 가능합니다.

여러종류의 오리들을 생산이 가능할수 있으므로 추상 팩토리 패턴을 사용하는것이 좋겠습니다.


abstract class AbstractDuckFactory{
abstract fun createMallardDuck() : Quackable
abstract fun createReadheadDuck() : Quackable
abstract fun createDuckCall() : Quackable
abstract fun createRubberDuck() : Quackable
}


추상팩토리 패턴을 통해 이렇게 각 다른 종류의 오리를 생성하는 함수를 만듭니다.



우선 데코레이터 이전에 데코레이터가 없는 오리를 만드는 팩토리부터 시작해 보겠습니다.

class DuckFactory : AbstractDuckFactory(){
override fun createMallardDuck(): Quackable = MallardDuck()
override fun createRedheadDuck(): Quackable = RedheadDuck()
override fun createDuckCall(): Quackable = DuckCall()
override fun createRubberDuck(): Quackable = RubberDuck()
}


이렇게 만들수 있고 이젠 실제로 사용하게될 팩토리를 만들어보겠습니다.


class CountingDuckFactory : AbstractDuckFactory(){
override fun createMallardDuck(): Quackable = QuackCounterDecorator(MallardDuck())
override fun createRedheadDuck(): Quackable = QuackCounterDecorator(RedheadDuck())
override fun createDuckCall(): Quackable = QuackCounterDecorator(DuckCall())
override fun createRubberDuck(): Quackable= QuackCounterDecorator(RubberDuck())
}


시뮬레이터에서는 어떤 객체가 리턴되었는지는 모릅니다. 단지 Quackable이라는 것을 받았다고 생각할뿐이죠

더불어 꽥학자들은 오리들의 꽥의 횟수를 셀 수 있습니다.



fun main(duckFactory: AbstractDuckFactory) {
val mallardDuck: Quackable = duckFactory.createMallardDuck()
val readheadDuck: Quackable = duckFactory.createRedheadDuck()
val duckCall: Quackable = duckFactory.createDuckCall()
val rubberDuck: Quackable = duckFactory.createRubberDuck()
val gooseDuck: Quackable = GooseAdapter(Goose())


그리고 이렇게 바뀐 시뮬레이터를 통해 테스트가 가능합니다. 



**연필깎이

조금 나아지긴 했지만 여전히 거위를 만들 때는 구상클래스에 의존해서 직접 만들어야만 합니다. 

거위를 위한 추상팩토리를 만들어주세요


override fun createGooseDuck(): Quackable = GooseAdapter(Goose())

이걸 만듬으로서 생성이 가능하겠죠?




**알아가기3

이 많은 오리들을 관리하기가 힘들어지므로 일괄적으로 관리한느 방법이 없을까요? 왜 오리를 하나씩 일일이 관리를 해야하는 것일까? 라는 질문입니다.

그래서 컬렉션을 다루어 한꺼번에 작업해보려고 합니다 어떤패턴을 쓰면 될까요?


Quackable 떼를 만들어석관리하면 됩니다. Composite 패턴 기억나시나요? Quackable 떼를 다룰때에 이패턴을 이용하면 됩니다.


컴포지트 패턴이죠. 복합객체와 임 원소에서 똑같은 인퍼테이스를 구현해야하 되는것이죠


class Flock : Quackable{
val quackers = ArrayList<Quackable>()

fun add(quacker : Quackable){
quackers.add(quacker)
}

override fun quack() {
val iterator : Iterator<Quackable> = quackers.iterator()
while(iterator.hasNext()){
val quacker : Quackable = iterator.next()
quacker.quack()
}
}
}



이렇게 만들어진것을


val flockOfDucks : Flock = Flock()

flockOfDucks.add(mallardDuck)
flockOfDucks.add(redheadDuck)
flockOfDucks.add(duckCall)
flockOfDucks.add(rubberDuck)
simulate(flockOfDucks)


이렇게 바꿈으로 한꺼번에 관리가 가능해집니다.




**알아가기4

이번에는 각각의 오리들을 관리하고 싶다고합니다. 오리들을 실시간 추적 할 수 있는 기능을 만들어달라고 합니다.

망할 발주처... 적당히 말을 바꿔야지가 아니라 저희는 객체마을의 을이니까 진행을 해봅시다.



오리의 개별 행동을 관찰 딱 관찰이란 말에서 옵저버 패턴이 떠오르게 됩니다.

관찰이 가능한 대상이 Observable이 되는거고 저기에 옵저버를 등록하는 메소드와 연락을 돌리는 메소드가 있어야합니다.


interface QuackObservable{
fun registerObserver (observer : Observer)
fun notifyObserver()
}


옵저버블 인터페이스를 만들고


interface Quackable : QuackObservable{
fun quack()
}


여기에 Quackable에 구현함으로써 Quackable에서 모든 메소드를 구현하도록 만듭니다.


옵져버블을 일일이 등록 및 연락용 메소드를 구현하는 방법도 있지만


Observable 클래스를 통해 QuackObservable의 필요한 작업을 모두 위임해보도록 하겠습니다.


class Observable(val duck :QuackObservable) : QuackObservable{
val observers = ArrayList<Observer>()

override fun registerObserver(observer: Observer) {
observers.add(observer)
}

override fun notifyObserver() {
val iterator : Iterator<Observer> = observers.iterator()
while (iterator.hasNext()){
val observer = iterator.next()
observer.update(Observable(),duck)
}
}
}


이렇게 Observable에 위임하고 


class MallardDuck : Quackable {
val observable: Observable
init {
observable = Observable(this)
}
override fun registerObserver(observer: Observer) {
observable.registerObserver(observer)
}
override fun notifyObserver() {
observable.notifyObserver()
}
override fun quack() {
println("Quack")
notifyObserver()
}
}


MallardDuck에 구현합니다.


class QuackCounterDecorator(val duck: Quackable) : Quackable {
val observable : Observable
init {
observable = Observable(this)
}
companion object {
var numberOfQuacks = 0
}

override fun registerObserver(observer: Observer) {
observable.registerObserver(observer)
}

override fun notifyObserver() {
observable.notifyObserver()
}

override fun quack() {
duck.quack()
notifyObserver()
numberOfQuacks++
}
}

데코레이터도 이렇게 옵져버블 구현하면 되겠죠?



interface Observer{
fun update(duck : QuackObservable)
}
class Quacklogist : Observer{
override fun update(duck: QuackObservable) = println("Quackologist : " + duck + "just quacked")
}


이렇게 옵져버쪽만 구현하면 됩니다.