swift 디자인 패턴 - 전략패턴(스트래티지 패턴)

swift 디자인 패턴 - 전략패턴(스트래티지 패턴)

 

목표 : 알고리즘 변화를 캡슐화하기

 

아톰과 태권브이 로봇을 만들어보자.

 

-아톰의 기능: 주먹공격, 하늘날기

-태권브이의 기능: 미사일 공격, 걷기

아톰 클래스와 태권브이 클래스는 Robot이라는 추상 클래스의 자식클래스로 만들었다.

 

이유는 아톰과 태권브이는 둘다 공격(attack 메서드)와 이동 (move 메서드) 기능이 있는 로봇의 한 종류이기 때문이다.

 

아톰과 태권브이의 공격기능과 이동기능이 다르기 때문에 Robot 클래스에서 atack과 move 메서드를 추상 메서드로 설정해서 자식 클래스에서 정의하도록 했다.

protocol Robot {
    var name: String { get set }
    func attack()
    func move()
}


//태권브이
class TaekwonV : Robot{
    
    var name: String = ""
    
    init(name: String) {
        self.name = name
    }
    
    func attack() {
        print("나는 미사일로 공격한다.")
    }
    
    func move() {
        print("나는 걸을 수 있어")

    }
}

//아톰
class Atom : Robot{
    
    var name: String = ""
    
    init(name: String) {
        self.name = name
    }
    
    func attack() {
        print("나는 주먹으로 공격한다.")
    }
    
    func move() {
        print("나는 날수 있어")
    }
}



let taekwonV = TaekwonV(name: "태권브이1")
print("나의 이름은 \(taekwonV.name) 입니다.")
taekwonV.move()
taekwonV.attack()

print("")

let atom = TaekwonV(name: "아톰1")
print("나의 이름은 \(atom.name) 입니다.")
atom.move()
atom.attack()

 

문제점

 

[1]기존 로봇의 공격 또는 이동 방법을 수정하려면 어떤 작업을 해야하나?

예) 아톰이 날 수는 없고 오직 걷게만 하고 싶다면? 또는 태권 V를 날게 하려면?

 

[2]새로운 로봇을 만들어 기존의 공격 또는 이동 방법을 추가하거나 수정하려면?

예)새로운 로봇으로 썬가드 클래스를 만들어서 태권V의 미사일 공격 기능을 추가하려면?

 

 

[시나리오1 ]기존 로봇의 공격과 이동 방법을 수정하는 경우

예를들어 아톰이 날 수는 없고 오직 걷게만 만들고 싶다면 Atom 클래스의 move 메서드를 수정해야 한다.

 

    func move() {
            print("나는 걸을 수 있어")
    }

 

이는 새로운 기능으로 변경하려고 기존 코드의 내용을 수정해야 하므로 OCP에 위배된다.

 

또한 Atom 클래스의 move 메서드와 TaekwonV 클래스의 move 메서드가 동일한 기능을 수행하므로 중복된다.

 

만약 move 메소드를 수정하려면 로봇의 종류 개수 만큼 중복된 모든 코드를 수정해야하는 문제점이 발생한다. 

 

 

[시나리오 2] 새로운 로봇에 공격/이동 방법을 추가/수정하는 경우

새로운 로봇을 추가할려면? 현재 설계를 보면 로봇 자체가 캡슐화 단위이기 때문에 새로운 로봇을 추가히기가 매우 쉽다.

 

아래와 같이 썬가드 클래스를 작성하고 이를 로봇의 서브 클래스로 두면 된다.

 

 

 

하지만 새로운 로봇에 기존의 공격 또는 이동 방법을 추가하거나 변경하려고 하면 문제가 발생한다.

예를들어 썬가드 클래스에 태권브이의 미사일 공격을 사용한다고하면 TaekwonV 클래스와 Sungard 클래스의 attack 메서드가 중복해서 사용된다.(나중에 심각한 문제가 될 수 있다.)

 

앞으로 로봇 기술이 발전해서 새로운 이동 기능과 공격 기능이 발명된다면 현재 로봇의 move, attack 메소드를 다 수정해야만 한다.

 

해결책

로봇 설계에서의 문제를 해결하려면 무엇이 변화되었는지를 찾아야 한다.

변화된것을 찾은 후에 이를 캡슐화해야 한다.

 

로봇예제에서 변화되면서 문제를 발생시키는 요인은 로봇의이동 방식과 공격방식의 변화다.

 

즉, 새로운 방식의 이동 및 공격 기능이 계속해서 추가될 수 있으므로 , 기존의 로봇이나 새로운 로봇이 이러한 기능을 별다른 코드 변경 없이 제공 받거나 기존의 공격이나 이동 방식을 다른 공격이나 이동 방식으로 쉽게 변경할 수 있어야 한다.

 

Key point : 무엇이 변화되었는지를 찾은 후에 이를 클래스로 캡슐화 한다.

 

로봇 예제에서 이동 기능과 공격 기능이 변한다는 것을 알고 있다.

따라서 이를 캡슐화하려면 외부에서 구체적인 이동 방식과 공격 방식을 담은 구체적인 클래스들을 은닉해야 한다.

이를 위해 공격과 이동을 위한 인터페이스를 각각 만들고 이들을 실제 실현한 클래스를 만들어야 한다.

 

-MovingStrategy는 이동 기능을 캡슐화하는 인터페이스

-AttackStrategy는 공격 기능을 캡슐화하는 인터페이스

 

 

클라이언트에서는 연관관계를 이용해 이동 기능과 공격기능의 변화를 포함 시킨다.

 

이 예에서는 Robot 클래스가 이동 기능과 공격 기능을 이용하는 클라이언트 역할을 수행한다.

 

이 클래스는 변화를 처리하기 위해서 MovingStrategy와 AttackStrategy 인터페이스를 포함해야 한다.

 

 

Robot 클래스의 입장에서 보면 구체적인 이동 방식과 공격 방식이 MovingStrategy와 AttackStrategy 인터페이스에 의해 캡슐화되어 있다.

 

따라서 이들 기능을 사용하는 로봇 객체와는 상관없이 앞으로 등장할 이동 방식과 공격 방식의 변화 뿐만 아니라 현재 변화도 잘 처리할 수 있게 된다.

 

새로운 공격 방식이 개발되면?

AttackStrategy 인터페이스가 변화에 대한 일종의 방화벽 역할을 수행해 Robot 클래스의 변경을 차단해 준다.

즉, 새로운 기능의 추가가 기존의 코드에 영향을 미치지 못하게 하므로 OCP를 만족하는 설계가 된다.

 

이렇게 변경된 새로운 구조에서는 외부에서 로봇 객체의 이동 방식과 공격 방식을 임의대로 바꿔주도록 하는 메서드가 필요하다.

 

이를 위해서 Robot 클래스에 set MovingStrategy와 setAttackStrategy 메서드를 정의해 로봇의 이동 방식과 공격 방식이 필요할 때 바꿀 수 있도록 한다. 이런 변경이 가능한 이유는 상속 대신 집약 관계를 이용했기 때문이다.

 

 

전략 인터페이스

//MARK: 이동 전략
//이동 전략 인터페이스
protocol MovingStrategy{
    func move()
}

//날기
class FlyingStrategy : MovingStrategy{
    func move() {
        print("i can fly")
    }
}

//걷기
class WalkingStrategy : MovingStrategy{
    func move() {
        print("i can walk")
    }
}


//MARK: 공격 전략
//공격 전략 인터페이스
protocol AttackStrategy{
    func attack()
}

//미사일
class MissileStrategy : AttackStrategy{
    func attack() {
        print("i have missile Atack!")
    }
}

//펀치
class PunchStrategy : AttackStrategy{
    func attack() {
        print("i have Punch Atack!")
    }
}

 

위에서 만든 전략을 사용하는 클래스 - 프로토콜로 구현

//MARK: Protocol로 구현
protocol Robot {
    var name: String? { get set }
    var movingStrategy:MovingStrategy? { get set }
    var attackStrategy:AttackStrategy? { get set }
    
    init(name:String)
    func attack()
    func move()
    
    /// 전략을 바꿀 수 있는 세터 메소드
    func setMovingStrategy(movingStrategy:MovingStrategy)
    func setAttackStrategy(attackStrategy:AttackStrategy)
}


//태권브이
class TaekwonV : Robot{

    var name: String?
    var movingStrategy: MovingStrategy?
    var attackStrategy: AttackStrategy?

    required init(name: String) {
        self.name = name
    }
    
    func attack() {
        attackStrategy?.attack()
    }
    
    func move() {
        movingStrategy?.move()
    }
    
    /// 전략을 바꿀 수 있는 세터 메소드
    func setMovingStrategy(movingStrategy: MovingStrategy) {
        self.movingStrategy = movingStrategy
    }
    
    func setAttackStrategy(attackStrategy: AttackStrategy) {
        self.attackStrategy = attackStrategy
    }
}

//아톰
class Atom : Robot{
    
    var name: String?
    var movingStrategy: MovingStrategy?
    var attackStrategy: AttackStrategy?

    required init(name: String) {
        self.name = name
    }
    
    func attack() {
        attackStrategy?.attack()
    }
    
    func move() {
        movingStrategy?.move()
    }
    
    /// 전략을 바꿀 수 있는 세터 메소드
    func setMovingStrategy(movingStrategy: MovingStrategy) {
        self.movingStrategy = movingStrategy
    }
    func setAttackStrategy(attackStrategy: AttackStrategy) {
        self.attackStrategy = attackStrategy
    }
}



let taekwonV = TaekwonV(name: "태권브이 protocol")
let atom = Atom(name: "아톰 protocol")

taekwonV.setMovingStrategy(movingStrategy: WalkingStrategy())   //걷기 전략
taekwonV.setAttackStrategy(attackStrategy: MissileStrategy())   //미사일 전략



atom.setMovingStrategy(movingStrategy: FlyingStrategy())        //날기 전략
atom.setAttackStrategy(attackStrategy: PunchStrategy())         //펀치 전략

print("################  protocol 로 구현  ################")
print("나의 이름은 \(taekwonV.name ?? "nil")")

taekwonV.move()
taekwonV.attack()



print("")


print("나의 이름은 \(atom.name ?? "nil" )")

atom.move()
atom.attack()

print("")

################  protocol 로 구현  ################

나의 이름은 태권브이 protocol

i can walk

i have missile Atack!

 

나의 이름은 아톰 protocol

i can fly

i have Punch Atack!

 

슈퍼클래스와 서브 클래스로 구현해보기

// ################################################    Robot을 class(상속) 로 구현 ################################################
//MARK: 상속으로 구현
class RobotSuperClass {
    var name:String?
    var movingStrategy: MovingStrategy?
    var attackStrategy: AttackStrategy?

    init(name: String) {
        self.name = name
    }
    
    func attack() {
        attackStrategy?.attack()
    }
    
    func move() {
        movingStrategy?.move()
    }
    
    /// 전략을 바꿀 수 있는 세터 메소드
    func setMovingStrategy(movingStrategy: MovingStrategy) {
        self.movingStrategy = movingStrategy
    }
    func setAttackStrategy(attackStrategy: AttackStrategy) {
        self.attackStrategy = attackStrategy
    }
}


class AtomSubClass:RobotSuperClass{
    override init(name: String) {
        super.init(name: name)
    }
}
class TaekwonVSubClass:RobotSuperClass{
    override init(name: String) {
        super.init(name: name)
    }
}


let taekwonInstance = TaekwonVSubClass(name: "태권브이 super")
let atomInstance = AtomSubClass(name: "아톰 super")

taekwonInstance.setMovingStrategy(movingStrategy: WalkingStrategy())
taekwonInstance.setAttackStrategy(attackStrategy: MissileStrategy())

atomInstance.setMovingStrategy(movingStrategy: FlyingStrategy())
atomInstance.setAttackStrategy(attackStrategy: PunchStrategy())

print("################  class(상속)로 구현  ################")

print("나의 이름은 \(taekwonInstance.name ?? "nil")")
taekwonInstance.move()
taekwonInstance.attack()


print("")

print("나의 이름은 \(atomInstance.name ?? "nil")")
atomInstance.move()
atomInstance.attack()

print("")

 

################  class(상속)로 구현  ################

나의 이름은 태권브이 super

i can walk

i have missile Atack!

 

나의 이름은 아톰 super

i can fly

i have Punch Atack!

 

 

스트래티지 패턴

 

스트래티지 패턴이란 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.

 

전략 : 어떤 목적을 달성하기 위해 일을 수행하는 방식, 비즈니스 규칙, 문제를 해결하는 알고리즘 등..

 

예) 게임 프로그래밍에서 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동하는 방식을 바꾸고 싶을 때 스트래티지 패턴을 사용하면 유리하다.

 

Key point : 스트래티지 패턴은 같은 문제를 해결하는 여러 알고리즘이 클래스 별로 캡슐화 되어 있고, 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴이다.

 

 

 

-Strategy : 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시한다.

 

-ConcreteStrategy1, 2, 3 : 스트래티지 패턴엣 명시한 알고리즘을 실제로 구현한 클래스다.

 

-Context : 스트래티지 패턴을 이용하는 역할을 수행한다.  필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메소드를 제공한다.

 

 

1.클라이언트가 원하는 스트래티지 객체를 생성한다.

2.이를 Context 객체에 바인딩 한다.

3.Context 객체는 바인딩된 스트래티지 객체의 타입에 따라 적절한 행위를 실행한다.

 

 

test.zip
0.02MB

 

참고 : JAVA 객체지향 디자인 패턴