ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인패턴 (1) - 전략패턴
    언어 2023. 9. 7. 16:38

    1. 전략 패턴이란? 

     

    -> 객체가 할 수 있는 행위 분리 (인터페이스화)

    -> 행위에 대한 구현체를 생성하고, 구체적 행위 정의  (캡슐화) 

    -> 추후 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 

          전략(구현체)을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법을 말합니다.

     

    - 여기서 가장 중요한 두가지 과정은 아래와 같다.

    1. 기존 행위 중 변화할 수 있는 특정 행위를 분리 -> 인페이스화 

    2. 행위에 대한 구현객체와 구상관계 만들기

     

     

    > 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.

       전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.

     

     

     

    2. 오리 예제 

     

    - 오리를 생성해야하는데, 상황에 따라 오리가 가지는 특성이 다르고, 이는 계속 변할 수 있다.

     

    2.1 상속을 통해 해결해 보기 

     

     - Duck 클래스에 quack(),swim(),display()가 정의되어있다. (display()는 추상)

     - 자식 클래스인 MallerdDuck,RedheadDuck는 display()구현하고 있다.

     

    public abstract class Duck{
    
    
    	public void quack(){
        	//..꽥소리
        }
        
        public void swim(){
        	//..수영중
        }
        public void fly(){
        	//.. 날다
        }
        
        public abstract void display()
    
    }
    
    public class MallerdDuck extends Duck {
    	public void display(){
        	//mallerd의 생김새 
        }
    }
    
    public classs RedheadDuck extends Duck {
    	public void display(){
        	//RedheadDuck의 생김새 
        }
    }

     

    -> 이 상황에서 날 수 없는 오리와 소리 낼 수 없는 오리들이 추가된다고 해보자.

    -> 문제는 둘 다 오리를 상속받기때문에 fly()나 quack()가 추가된다. 

    -> 물론! 오버라이딩을 통해 새로 생성된 오리들의 fly()나 quack()을 수정해줘도됨

    -> 하지만 이는 매우 번거로운 일 (오리를 추가할 때 마다 바꿔줘야함)

     

    * 상속은 코드 중복을 줄 일 수 있는 좋은 방법이지만,

       하위 타입에 일괄 적용되어, 상속할 필요가 없는 부분까지하게 됨 

       

    2.2 인터페이스를 통해 해결해보기 

     

    public interface Flyable{
     public void fly()
    }
    
    public interface Quackable{
     public void quack()
    }
    
    public class MallerdDuck extends Duck implement Flyable,Quackable {
    	public void display(){
        	//mallerd의 생김새 
        }
        public void fly(){
        	//날다
        }
         public void quack(){
         	//울다
         }
    }
    
    public classs RedheadDuck extends Duck implement Flyable,Quackable {
    	public void display(){
        	//RedheadDuck의 생김새 
        }
         public void fly(){
         	//날다
         }
        
         public void quack(){
         //울다
         }
    }
    
    public class RubberDuck extends Duck implement Quackable{
    	public void display(){
        	//RedheadDuck의 생김새 
        }
        
         public void quack(){
         //울다
         }
    }

     

    - 기존 Duck에 있던, fly와 Quackable을 인터페이스로 분리하고

    - 날 수 있는, 혹은 꽥 거릴 수 있는 클래스만 이를 구현하도록 만들었다.

     

    -->  변할 수 있는 행위를 따로 빼고, 이를 각 자식 클래스가 책임을 가지도록  캡슐화 (좋은 시도)

    -->  문제점 : 인터페이스를 구현할 때마다 세부내용을 바꿔줘야함 

                        클라이언트가 Duck 변수로만 각 인스턴스를 담아서 쓰고 싶을 때 fly()와 quack()호출 못함

                        그렇다면, 클라이언트는 Duck이 아닌, 각 자식 클래스를 의존 

     

    * 변화하기 쉬운 행위를 인터페이스화 하는 시도는 좋았다! 여기서 조금만 더 나아가면 된다.

     

    3. 오리예제에 디자인 패턴 적용

     

    - fly와 Quack 행위를 인터페이스화 하되, 각 오리는이 이를 직접 구현하는 것이 아니라

      미리 구현된 fly와 quack 콘크리트 클래스의존하는 형태로 바꿔보자

     (미리 구현된 전략 중 선택!)

     

    public interface Fly{
    	public void fly()
    }
    
    public class FlyWithWings implements Fly{
    	public void fly(){
        	//날개로 날아요
        }
    }
    public class FlyNoWay implements Fly{
    	public void fly(){
        	//못날아요 
        }
    }
    
    public interface Quackable{
    	public void quack()
    }
    
    public class Quack implements Quackable{
      
    	public void quack(){
        	//콱
        }
    }
    
    public class Squack implements Quackable{
      
    	public void quack(){
        	//고무오리의 콱
        }
    }
    
    public class Mute implements Quackable{
      
    	public void quack(){
        	//조용
        }
    }
    public class Duck {
    	Flyable flyable;
        Quakable quackable;
        
    	
        public void fly(){
        	flyable.fly();
        }
        public void quack(){
        	quackable.quack()
        }
    
    }
    
    public class MallarDuck extends Duck{
    
    	public mallarDuck(){
        	quackable = new Quack();
        	flyable = new FlyWithWings();
        }
        
        public void display(){
        	//생김새
        }
    }

     

    - duck은 변화하는 행위 (fly,quack)를 직접 구현하지 않고, 위임함 (책임분리)

     

    4. 캡슐화된 행동 살펴보기 

     

    - 오리들의 행동을 일련의 행동으로 생각하는 대신 알고리즘으로 생각 

    - 클라이언트에서 자주바뀌는 행위인 나는행동과 꽥꽥행동은 캡슐화된(클래스) 알고리즘으로 구현

     

    5. 두 클래스 합치는 방법

     

     - "a에는 b가 있다" 라는 관계 -> 인스턴스 변수로 가지고 있는것 

     - 이를 구성이라고도 함 

     - 구성(의존)은 디자인 원칙에서 매우 중요함

     

     

     

     

     

     

    *디자인 원칙 1 : 변화하는 부분을 찾아내서 분리한다.

                               달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화한다.

                               캡슐화하면, 내부구현에 자유가 생김 -> 추후에 바뀌는 부분만 고치거나 확장 가능

     

    *디자인 원칙 2: 상속보단 구성을 사용한다.

     

    * 디자인 원칙 3: 구현보다는 인터페이스에 맞춰서 프로그래밍하라 

       

    * 인터페이스에 맞춰서 프로그래밍하라는 말은 변수 상위 타입으로 선언하여 다형성을 최대한 활용하라는 것 

       메서드를 호출하는 쪽은 구체 클래스에 대해서는 몰라도 됨 

      - 변화하는 것과 클라이언트의 의존관계는 하위 구현 클래스가 아닌

      - 상위 인터페이스나 부모타입과 의존관계를 맺어야함을 의미 

     

    *  캡슐화 한다는 것은 내부구현을 감추고, 공용 인터페이스 호출만해서 사용할 수 있도록 바꾼다는 것  

      이를 통해 오류의 범위 축소, 자율성 확보(내부구현자유), 특정 데이터 감추기 가능  

     

    * 전략패턴특정 행위(책임)이 변화하기 쉬운 환경에서 적용할 수 있을 듯 하다. 

     

    * 인터페이스화 하는 것은 -> 특정 역할 (역할 수행자의 변화 가능성) ex DB 

                                             ->  특정 행동 (행동의 변화 가능성)

    -> 변화할 수 있는 부분을 인터페이스화 

    -> 인터페이스에는 당연히 콘크리트 클래스가 따르고 

    -> 클래스를 만드는 과정에서 캡슐화 이점 가져갈 수 있음!  

     

    * 인터페이스로 분리된 행동구현될 수도 있고, 구상될 수도 있음

     

     

    참고자료: 헤드퍼스트 디자인패턴

Designed by Tistory.