Computer Science/디자인 패턴

스트래티지 패턴(Stratagy Pattern)

닉네임못짓는사람 2022. 1. 7. 02:40
반응형

스트래티지 패턴


스트래티지 패턴은 행위(Behavior)들을 캡슐화하여 동적으로 자유롭게 바꿀 수 있게 하여

전략(Stratagy)을 쉽게 바꿀수 있게 해주는 패턴이다.

 

코드에서 달라지는 부분을 찾아내어 분리시킨 뒤, 이를 캡슐화한다.

상속보다는 구성을 활용하며, 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

 

예시


어떤 게임에 셋의 캐릭터가 존재한다고 생각해보자.

abstract class Player{
    fun attack(){ println("공격합니다.") }
    fun walk(){ println("걷습니다.") }
    abstract fun display()
}

class Warrior: Player(){
    override fun display(){
        println("전사입니다.")
    }
}

class Fighter: Player(){
    override fun display(){
        println("격투가입니다")
    }
}

class Wizard: Player(){
    override fun display(){
        println("마법사입니다.")
    }
}

 

이 캐릭터들은 공격과 걷기는 공통적으로 하기때문에 Player클래스에서 그대로 상속을 받고,

display는 서로 다르기 때문에 각 클래스에서 override해준다.

 

그런데 게임 업데이트에서 길에 있는 돌을 치워야 하는 이벤트가 추가되었다.

그래서 새로 push라는 함수를 Player클래스에 추가해주었다.

abstract class Player{
    fun attack(){ println("공격합니다.") }
    fun walk(){ println("걷습니다.") }
    fun push(){ println("돌을 밀어냅니다. ") }
    abstract fun display()
}

위와 같이 코드를 수정하고 게임을 업데이트 했는데, 문제가 발생했다.

마법사는 힘이 부족하여 돌을 밀어내지 못해야하는데,

모두 동일하게 Player클래스를 상속받기 때문에 마법사도 돌을 밀어낼 수 있게 된 것이다.

 

이에 개발자는 대수롭지 않게 마법사 클래스에서 push함수를 override해버렸다.

class Wizard: Player(){
    override fun display(){
        println("마법사입니다.")
    }
    override fun push(){
        println("힘이 부족해 밀지못합니다.")
    }
}

위와 같이 수정하면 당장 지금의 상황은 넘어갈 수 있겠지만,

 

만약 이후에 다시 책을 읽는 이벤트가 추가되었다고 생각해보자.

마법사는 책의 내용을 완전히 이해하지만, 전사와 격투가는 지능이 부족해서 이해가 불가능하다.

그러면 또다시 전사와 격투가 클래스에서 해당 함수를 override해주어야 할 텐데

 

이후로 캐릭터가 추가되고, 이벤트가 추가됐을 때마다

위와 같이 수많은 클래스에서 수많은 함수들을 모두 override하는건 매우 비효율적일것이다.

 

때문에 우리는 이럴때 스트래티지 패턴을 사용하여 이러한 동작들을 분리하고, 캡슐화한다.

 

Player클래스에서 공격과 걷기는 모든 캐릭터가 동일하게 수행하니 놔두고,

책 읽기와 돌 밀어내기를 분리하여 캡슐화해보도록 하자.

interface PushRock{
    fun push()
}

class PushSuccess: PushRock{
    override fun push(){ println("돌을 밀어냅니다.") }
}

class PushFail: PushRock{
    override fun push(){ println("힘이 부족해 밀지못합니다.") }
}

interface ReadBook{
    fun read()
}

class ReadSuccess: ReadBook{
    override fun read(){ println("책을 읽고 이해했습니다.") }
}

class ReadFail: ReadBook{
    override fun read(){ println("책을 이해하지 못했습니다.") }
}

먼저, 위와 같이 돌 밀기와 책 읽기를 interface로 구현하고,

각각의 성공, 실패 클래스에서 이들을 따로 구현하도록 만들자.

 

그 후, Player클래스를 수정하여 책 읽기와 돌 밀어내기 동작을 interface로 위임한다.

abstract class Player{
    lateinit var readBook: ReadBook
    lateinit var pushRock: PushRock
    
    fun attack(){ println("공격합니다.") }
    fun walk(){ println("걷습니다.") }
    fun read(){ readBook.read() }
    fun push(){ pushRock.push() }
    abstract fun display()
}

다음으로 Player클래스를 상속받는 각 캐릭터 클래스의 생성자에서

캐릭터가 해당 동작을 수행할 수 있을지를 정해준다.

class Warrior: Player(){
    init{
        readBook = ReadFail()
        pushRock = PushSuccess()
    }
    
    override fun display(){
        println("전사입니다.")
    }
}

class Fighter: Player(){
    init{
        readBook = ReadFail()
        pushRock = PushSuccess()
    }
    
    override fun display(){
        println("격투가입니다")
    }
}

class Wizard: Player(){
    init{
        readBook = ReadSuccess()
        pushRock = PushFail()
    }
    
    override fun display(){
        println("마법사입니다.")
    }
}

마지막으로 메인 함수에서 객체들을 만들어서 각 동작을 수행시켜보자.

fun main(){
    var warrior = Warrior()
    var fighter = Fighter()
    var wizard = Wizard()
    warrior.push()
    warrior.read()
    fighter.push()
    fighter.read()
    wizard.push()
    wizard.read()
}

장점


  • 변경되는 부분만을 따로 때어내기 떄문에 코드 중복을 방지할 수 있다.
  • 알고리즘(기능)의 대체가 쉽다.
  • 화장성이 좋아 알고리즘(기능) 추가가 쉽다.
  • 클라이언트와 독립적이기 때문에 알고리즘(기능) 변경에도 강하다.

단점


  • 알고리즘(기능)이 늘어날수록 객체가 계속해서 늘어난다.
  • 많아진 객체들의 정체를 일일이 알기가 힘들고, 복잡해질 수 있다.
반응형

'Computer Science > 디자인 패턴' 카테고리의 다른 글

빌더 패턴(Builder Pattern)  (0) 2022.02.02
옵저버 패턴(Observer Pattern)  (0) 2022.01.25
스테이트 패턴(State Pattern)  (0) 2022.01.08
싱글톤 패턴(SingleTon Pattern)  (0) 2021.12.16