[알고쓰면 좋은 디자인 패턴] 전략 패턴

1

안녕하세요! 오늘부터 디자인 패턴에 대해 조금씩 알아보겠습니다.
오늘의 주제는 전략패턴인데요!
어렵지 않으니 편하게 Follow me~ 하시면 되겠습니다.

1. 전략패턴이란 무엇인가?

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

  • 특정한 계열의 알고리즘들을 정의하고
  • 각 알고리즘을 캡슐화하며
  • 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.
    전략은 알고리즘을 사용하는 클라이언트와는 독립적으로 다양하게 만든다.

설명에 따르면, 알고리즘 자체가 객체가 어떻게 행동하는지에 대한 전략이 된답니다.

2. 전략패턴의 구성도


전략 패턴은 다음과 같은 구성요소를 갖고있습니다.
하나하나 차근차근 알아봅시다.

  • 전략 사용자(context) : 전략을 사용하는 프로그램의 흐름
  • 전략(strategy) : 구체화된 여러 알고리즘들의 추상화
  • 전략 콘크리트(Concrete strategy) : 알고리즘의 실제 구현체
  • 전략 제공자(Client) : 전략 사용자에게 전략 콘크리트를 주입하는 역할

3. 전략패턴 적용전 코드

그렇다면, 구성요소들을 어떻게 구현하고 적용할수 있는지 알아봅시다.
비행기 게임을 만든다고 가정합시다!

아래의 코드는 비행기 클래스입니다.

data class Flight(
    private val hp: Int = 100,
) {
    fun attack() {
        println("기관총 공격")
    }


    fun move() {
        // 이동 가능
    }

현재 비행기 게임에는 기관총 공격밖에 없는데, 클라이언트가 미사일공격을 만들어달라고 요청했습니다.

data class Flight(
    private val hp: Int = 100,
) {

 //if를 통해 attack 메소드를 수정 하였다.
    fun attack(isBomb : Boolean) {
        if(isBomb) println("폭탄 공격 ") else println("기관총 공격")
    }

    fun move() {
        // 이동 가능
    }


}

val defaultFlight  = Flight() //일반 비행기
defaultFlight.attack(isBomb = false)

val lockectFlight = Flight() //로켓 아이템먹은 비행기
lockectFlight.attack(isBomb = true)

현재 attack메소드를 수정하여, 로켓공격을 만들었습니다.

그런데 이번엔 폭탄 공격을 만들어달라고 클라이언트가 요청했습니다.

그런데 또 attack 메소드를 수정해야하니 변경이 일어나니 무기가 추가될때마다 계속 수정을 해야합니다.

또한 이러한 코드는 OCP(개방폐쇄 원칙)에 위배됩니다.

OCP란?

OCP란?
Open Close Principle : 개방폐쇄의 원칙
시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 로버트 마틴이 명명한 객체지향설계 5대 원칙 SOLID중 하나입니다.
OCP는 소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방(OPEN)되어야 하지만 변경에 대해서는 폐쇄(CLOSE)되어야 한다는 의미입니다.
즉, 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 의미입니다.

그렇다면 기존의 코드를 유지하면서도, 새로운 기능을 추가하는 방법은 무엇이있을까요??

전략패턴을 적용하자!

이제 본론입니다.
전략패턴을 쉽게 말하자면, 변경되는 부분을 인터페이스 뒤로 캡슐화시킨다고 생각하면 됩니다.

//strategy에 해당한다.
interface AttackStrategy {
    fun attack()
}

//Concrete strategy에 해당한다.
// 기관총 공격
internal  class DefaultAttackStrategy : AttackStrategy {
    override fun attack() {
        print("기관총 발사")
    }
}

// 폭탄 공격
internal class BoomAttackStrategy : AttackStrategy {
    override fun attack() {
        println("폭탄 발싸")
    }
}

// 로켓 공격
internal class RocketAttackStrategy : AttackStrategy {
    override fun attack() {
        println("로켓 발사")
    }
}

전략패턴을 적용한다면, 공격 기능의 인터페이스가 만들어져야겠죠.

data class Flight(
    private val hp: Int = 100,
) {
    //전략사용자에게 전략 콘크리트를 주입해주는 Client
    fun attack(attackStrategy : AttackStrategy ) {
        attackStrategy.attack()
    }

    fun move() {
        // 이동 가능
    }

}

그리고 해당 전략 콘크리트를 주입받도록 attack을 변경합니다.

        val defaultFlight  = Flight() //일반 비행기
        defaultFlight.attack(attackStrategy = DefaultAttackStrategy())


        val lockectFlight = Flight() //로켓 아이템먹은 비행기
        lockectFlight.attack(attackStrategy = RocketAttackStrategy())

이렇게 전략패턴을 통해 구현 시, 전략이 바뀔때마다 기존코드가 수정되는 불상사를 막을수 있습니다.

하지만 모든 디자인패턴이 그렇듯 단점또한 가지게 되는데, 전략이 추가될때마다 class가 늘어나기때문에 요구사항의 변경으로 변경될 여지가 있을때 전략 패턴을 고려하는것이 중요합니다.

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글