ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (1) Strategy Pattern
    프로그래밍/Design Pattern 2023. 3. 5. 02:43

    설명

     

    1. 새로운 기획에서 앱 화면에 오리(duck)를 그려달라고 하여, 오리 클래스를 만들게 되었다고 가정합니다.

    또 오리를 눌렀을 때 오리가 꽥꽥 내는 소리와 나는 동작을 표현해야 한다고 하면 코드 짰을 때 대략 다음과 같을 것입니다.

    class Duck {
    
        func quack() {
    	    // 꽥꽥
        }
    	
        func fly() {
        	// 나는 동작
        }
        ...
    }

     

    2. 그 다음주 기획에서 타겟A군의 유저에게는 청둥오리, B군에는 검은오리가 있어야 한다고 합니다.

    또한 이 오리들의 우는 소리와 나는 효과가 달라야 한다고 합니다.

    이 경우 개발자는 간단히 오리 클래스를 상속하여 각 오리 클래스를 구현하는 선택을 할 수 있습니다.

     

    구체적으로 다음과 같습니다.

    (1) 상속을 사용 -  Duck 클래스를 상속받은 MalladDuck, BlackDuck 클래스를 만들고, 함수 quack()과 fly()를 오버라이드하여 새로운 행동을 만드는 방법

    class Duck {
    
        func quack() {
    	    // 꽥꽥
        }
    	
        func fly() {
        	// 나는 동작
        }
        ...
    }
    
    class BlackDuck: Duck {
    
        override func quack() {
    	    // 꽥꽥 재정의
        }
    	
        override func fly() {
        	// 나는 동작 재정의
        }
        ...
    }

    지금처럼 상속을 사용했을 때 단점은 무엇이 있을까요?

    이 방법의 문제는 실수 할 가능성이 크다는 것입니다.

    만약 계속해서 새로운 종류의 오리가 추가가 되어 아무 동작을 하지 않아야 하는 경우, 재정의 한 함수를 비워 두어야 할 것 입니다.

    이 경우 전체 기획을 제대로 알지 못하는 다른 사람이 그 부분을 보고 수정을 하게 될 때, 그부분에 왜 비워두었는지 같이 이유을 주석으로 달지 않았다면 큰 실수를 하게 될 수도 있습니다.

     

    그러면 상속을 사용하지 않는다면 어떨까요?

    (2) 상속을 사용하지 않음 - Duck클래스에서 함수 quack(), fly()를 제외하고 각각 오리 클래스에서 함수를 구현

    이때 protocol을 사용하여 각 오리마다 Quackable, Flyable를 구현하게끔 강제할수도 있을 것 입니다.

    class Duck {
    	...
    }
    protocol Quackable {
        func quack()
    }
    protocol Flyable {
        func fly()
    }
    
    class BlackDuck: Duck, Quakable, Flyable {
        func quack() {
    	    // 꽥꽥 구현
        }
    	
        func fly() {
        	// 나는 동작 구현
        }
        ...
    }

    이 방법의 문제중 하나는 코드 재사용성이 많이 떨어진다는 점입니다. 결과적으로 같은 동작을 하는 오리라도 반드시 구현부분을 채워주어야 합니다.

    이러한 재사용성이 떨어지는 문제를 해결하기위해 공통함수를 만들어 사용한다고 하면, 변경사항이 발생 될 때마다 공통함수 부분을 변경하게 되어 불필요하게 다른 모든 오리가 들어간 화면동작을 재점검해야 될 수도 있습니다.

     


    Strategy Pattern (전략 패턴)은 달라지는 행동들을 정의하고 각각을 캡슐화하여 유연성을 확보하는 패턴입니다.

     

    위 문제에서 오리가 우는 행동과 나는 행동은 계속해서 변경이 되는 영역입니다.

    따라서 이 바뀌는 부분을 분리한다면 나머지 부분은 변경되지 않기 때문에 앞으로의 수정은 없을 것 입니다.

    import Foundation
    
    class Duck {
        var quackBehavior: Quackable
        var flyBehavior: Flyable
        
        func performQuack() {
            quackBehavior.quack()
        }
        
        func performFly() {
            flyBehavior.fly()
        }
        
        ...
        
        init(quackBehavior: Quackable, flyBehavior: Flyable) {
            self.quackBehavior = quackBehavior
            self.flyBehavior = flyBehavior
        }
    }
    protocol Quackable {
        func quack()
    }
    protocol Flyable {
        func fly()
    }
    
    class BlackDuck: Duck {
        func setQuackBehavior(_ newBehavior: Quackable) {
            self.quackBehavior = newBehavior
        }
        func setFlyBehavior(_ newBehavior: Flyable) {
            self.flyBehavior = newBehavior
        }
    }

    위 코드에서는 오리마다 자주 변경될 수 있는 행동(quack, fly)을 캡슐화한 예시 입니다.

    여기서 Duck 클래스의 변경되는 함수 부분의 구현은 각각의 프로토콜에 맞춰서 구현이 되어있습니다. 이렇게 함으로써 각 행동 세부 구현이 변경 될 때마다 Duck 클래스가 더이상 변경될 필요가 없게 되었습니다.

     

    class MuteQuack: Quackable {
        func quack() {
            // mute
        }
    }
    class BarkQuack: Quackable {
        func quack() {
            // dog bark
        }
    }

    또 캡슐화를 한 덕택에 개별 Duck 클래스(BlackDuck)에서는 외부로부터 즉각적으로 행동을 주입받아 동작을 변경할수 있게 되었습니다.

    만일 조용히 우는 행동이 필요하다면 위 코드에서 MuteQuack 객체를 setQuackBehavior 함수를 통해서 넣어주면 됩니다.

    또한 같은 행동을 한다면 (2) 상속을 사용하지 않음 처럼 매번 구현부를 작성하는 것이 아니라, 단순히 사용하면 되기 때문에 코드 재사용성도 높아졌습니다. 

     

    이 과정을 따라가는 원칙을 다시 정리하면 아래와 같습니다.

     

    원칙1) 달라지는 부분을 찾아내어, 달라지지 않는 부분으로부터 분리한다.

    원칙2) 행동을 구현이 아닌, 인터페이스(프로토콜)에 "맞추어서 사용" 한다.

    원칙3) 상속이 아닌 구성(Composition)을 활용한다.

     

    정리

     

    Strategy Pattern (전략 패턴, = Policy Pattern)

    특정한 행위들을 정의하고, 각 행위을 캡슐화하여 이 행위들을 해당 계열 안에서 상호 교체가 가능하게 하는 패턴

     

    객체지향 원칙

    원칙1) 달라지는 부분을 찾아내어, 달라지지 않는 부분으로부터 분리한다.

    원칙2) 행동을 구현이 아닌, 인터페이스(프로토콜)에 "맞추어서 사용" 한다.

    원칙3) 상속이 아닌 구성(Composition)을 활용한다.

     

     

    반응형

    '프로그래밍 > Design Pattern' 카테고리의 다른 글

    (3) Decorator Pattern  (0) 2023.03.13
    (2) Observer Pattern  (0) 2023.03.05

    댓글

Designed by Tistory.