정상에서 IT를 외치다

[디자인패턴] 스트래티지 패턴 본문

디자인패턴

[디자인패턴] 스트래티지 패턴

Black-Jin 2019. 4. 24. 16:08
반응형

안녕하세요. 블랙진입니다.

책 HeadFirstDesignPattern 을 보며 코드를 Kotlin 으로 바꿔가며 공부한 내용입니다.


스트래티지 패턴(strategy pattern)


전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은

  • 특정한 계열의 알고리즘들을 정의하고
  • 각 알고리즘을 캡슐화하며
  • 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.

전략은 알고리즘을 사용하는 클라이언트와는 독립적으로 다양하게 만든다.[1] 전략은 유연하고 재사용 가능한 객체 지향 소프트웨어를 어떻게 설계하는지 기술하기 위해 디자인 패턴의 개념을 보급시킨 디자인 패턴(Gamma 등)이라는 영향력 있는 책에 포함된 패턴들 가운데 하나이다.

출처 - 위키



- 알로리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.


- 스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.



예제


오리 시물레이션 게임이 있습니다. 오리는 각각의 외형을 가지고 있고 소리를 내고 수영을 할 줄 압니다.

abstract class Duck {

abstract fun display()

fun quack() {
println("꽥꽥 소리를 낸다")
}

fun swim() {
println("수영을 한다")
}
}


오리 클래스를 상속하는 빨간머리오리, 고무오리, 가짜오리 세마리가 있습니다. 


1. 고무오리는 꽥꽥이 아닌 삑삑 소리를 냅니다.

2. 가짜오리는 소리를 내면 안됩니다.


그러면 각각의 조건에 맞는 오리를 만들어 보겠습니다.

class RedHeadDuck: Duck() {

override fun display() {
println("-- 빨간 머리 오리")
}
}

소리도 내고 수영도 가능한 빨간 오리입니다.


class RubberDuck: Duck() {

override fun display() {
println("-- 고무 오리")
}

override fun quack() {
println("삑삑 소리를 낸다")
}


}

고무오리는 삑삑 소리를 냅니다.


class Decoy: Duck() {

override fun display() {
println("-- 가짜 오리")
}

override fun quack() {
//아무것도 하지 않도록 오버라이드
}
}

가짜 오리는 소리를 내지 않습니다.



1. 문제 : 날기 기능을 추가해야 합니다.


Super 클래스인 Duck 에 fly() 함수만 추가해주면 해결됩니다.


abstract class Duck {

abstract fun display()

open fun quack() {
println("꽥꽥 소리를 낸다")
}

fun swim() {
println("수영을 한다")
}

//TODO 1. 날기 기능이 추가
open fun fly() {
println("날 수 있다.")
}
}

하지만 고무 오리와 가짜 오리는 날기 기능이 없어야 합니다. 그런 경우 함수를 오버라이드 해서 기능을 없애주어야 합니다.


//TODO 2. 고무오리는 날 수 없습니다.
class RubberDuck: Duck() {

override fun display() {
println("-- 고무 오리")
}

override fun quack() {
println("삑삑 소리를 낸다")
}

override fun fly() {
//아무것도 하지 않도록 오버라이드
}
}

이런 경우 새로운 기능이 추가 될 때마다 서브 클래스에 기능을 그때 그때 정의해 주어야합니다. 이는 같은 코드를 반복해야되고 프로그래머의 실수를 유발할 수 있습니다.



해결 1 : 인터페이스 나눠서 구분하기

interface Flyable {
fun fly()
}

interface Quackable {
fun quack()
}

Flyable, Quqckable 두 개의 인터페이스로 나눠 코드를 재사용 할 수 있게 합니다. 하지만 이 방법 또한 행동을 바꿀 때 마다 그 행동이 정의되어 있는 모든 서브 클래스들을 전부 수정해야 합니다.



책에서는 3가지 디자인 원칙을 제시해 줍니다.


1. 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.

2. 상속보다는 구성을 활용한다.

3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.


해결 2 : 각각 기능에 따른 구현 클래스를 만들어 줍니다.

interface FlyBehavior {
fun fly()
}

class FlyWithWings: FlyBehavior {
override fun fly() {
println("날 수 있다.")
}
}

class FlyNoWay: FlyBehavior {
override fun fly() {
println("날 지 못한다.")
}
}

FlyBehavior 인터페이스를 구현하는 두 개의 행동 구현 클래스를 만들어 줍니다.


interface QuackBehavior {
fun quack()
}

class Quack: QuackBehavior {
override fun quack() {
println("꽥꽥 소리를 낸다.")
}
}

class Squack: QuackBehavior {
override fun quack() {
println("삑삑 소리를 낸다")
}
}

class MuteQuack: QuackBehavior {
override fun quack() {
println("<<< 조용 >>>")
}
}

QuackBehavior 인터페이스를 구현하는 세 개의 행동 구현 클래스를 만들어 줍니다.


우리는 두가지 구현 클래스를 가지고 동적으로 Duck의 행동 전략을 쉽게 바꿔 줄 수 있습니다.


abstract class Duck {

private var flyBehavior: FlyBehavior? = null
private var quackBehavior: QuackBehavior? = null

abstract fun display()

fun setFlyBehavior(flyBehavior: FlyBehavior) {
this.flyBehavior = flyBehavior
}

fun setQuackBehavior(quackBehavior: QuackBehavior) {
this.quackBehavior = quackBehavior
}

fun performFly() {
flyBehavior?.fly()
}

fun performQuack() {
quackBehavior?.quack()
}

fun swim() {
println("수영을 한다")
}

}


setFlyBehavior() , setQuackBehavior() 함수를 통해 우리는 전략적으로 원하는 행동을 추가해 줄 수 있습니다. 

class RedHeadDuck: Duck() {

override fun display() {
println("-- 빨간 머리 오리")
}
}

class RubberDuck: Duck() {

override fun display() {
println("-- 고무 오리")
}
}

class Decoy: Duck() {

override fun display() {
println("-- 가짜 오리")
}

}

각각의 오리들은 오직 display 만 구현하면 됩니다. 이제 더이상 Duck() 을 상속하는 여러 오리들에게 각각 특징에 맞는 기능을 오버라이드 해주지 않아도 됩니다. 각 오리의 기능은 동적으로 코드를 재사용해 추가해 줄 수 있게 되었습니다.


그럼 가짜 오리에 대해서만 구현 테스트를 진행 해보겠습니다.


with(Decoy()) {
display()
swim()

//날 수 없다.
setFlyBehavior(FlyNoWay())
//소리를 못 낸다.
setQuackBehavior(MuteQuack())

performFly()
performQuack()
}

로그를 확인해 보면  아래와 같습니다.


-- 가짜 오리

수영을 한다

날 지 못한다.

<<< 조용 >>>


저희가 원하는 대로 동작을 했습니다. 



추가 : 로켓 추진을 달 수 있는 모형 오리를 추가


그럼 이번에는 로켓 추진을 달 수 있는 모형 오리를 추가해 보겠습니다.

class ModelDuck: Duck() {

init {

//모형 오리는 못납니다.
setFlyBehavior(FlyNoWay())
//모형 오리는 꽥꽥 소리를 냅니다.
setQuackBehavior(Quack())
}

override fun display() {
println("-- 모형 오리")
}

}

기본적으로 모형 오리는 날 수 없고 꽥꽥 소리를 낼 수 있습니다. 그럼 FlyBehavior에 로켓엔진을 추가해 보겠습니다.


class FlyRocketPowered: FlyBehavior {
override fun fly() {
println("로켓 추진으로 납니다.")
}

}

FlyBehavior을 구현하는 로켄엔진을 만들었습니다. 이를 날 수 없는 모형 오리에게 동적으로 추가해 주겠습니다.


with(ModelDuck()) {
display()
swim()

performFly()
performQuack()

//로켓 추진을 달아 주었습니다.
setFlyBehavior(FlyRocketPowered())
performFly()
}

로그를 확인해 보면 아래와 같습니다.


-- 모형 오리

수영을 한다

날 지 못한다.

꽥꽥 소리를 낸다.

로켓 추진으로 납니다

반응형
Comments