스트래티지 패턴 - 디자인 패턴 [ep 1]
출처 링크: https://www.youtube.com/watch?v=v9ejT8FO-7I&t=1565s&ab_channel=ChristopherOkhravi
관련 도서: http://www.yes24.com/Product/Goods/1778966
Head First Design Patterns - YES24
정말 쿨~ 하게 배우는 디자인 패턴 학습법다른 사람들이 뭔가를 만들어 놓았고, 누구든 마음대로 사용해도 되는 게 있다면 굳이 고생해서 똑같은 걸 만들어 써야 할 필요는 없을 것이다. 소프트
www.yes24.com
아래 내용은 제 100% 주관적인 내용입니다. 아래 내용이 잘못 되어도 어떠한 책임도 지지 않겠습니다.
비판적으로 내용을 받아들여주세요.
디자인 패턴 UML
디자인 패턴 Sample Code
샘플 코드 출처 : https://jusungpark.tistory.com/7
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void swim(){
System.out.println("물에 떠있습니다.");
}
public abstract void display();
public void performQuack(){
quackBehavior.quack();
}
public void performFly(){
flyBehavior.fly();
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("난다!!");
}
}
public class FlyNoWay implements FlyBehavior{
@Override
public void fly(){
System.out.println("날지못해요.");
}
}
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("꿱꿱.");
}
}
public class Squack implements QuackBehavior {
@Override
public void quack(){
System.out.println("삑삑.");
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack(){
System.out.prinln("조용.");
}
}
public class MallardDuck extends Duck {
public MallardDuck(){
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display(){
System.out.println("청둥오리 입니다.");
}
}
public class MiniDuckSimulator{
public static void main(String[] args){
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
mallard.setFlyBehavior(new FlyNoWay());
mallard.performFly();
}
}
내가 느낀 이 패턴의 특징
스트래티지 패턴은 만들고 싶은 대상(Duck)를 다양한 형태로 만들고 싶을 때,
그리고 그 대상을 구성하는 것들(나는 것과 관련된 기능, 꽥꽥 우는 것과 관련된 기능)을 다른 클래스로 위임하고 싶을 때 사용한다.
이렇게 만들고 나면 내가 Duck 클래스의 인스턴스를 만들고자 할 때, 내가 원하는 기능들을 하나씩 선택해서 만들면 된다.
예를 들어 flyBehavior 필드에는 FlyNoWay 인스턴스를 주고 quackBehavior에는 Quack 인스턴스를 주면 날지 못하지만 꽥꽥 소리내어 우는 오리가 완성된다.
이 외에도 날지 못하고 울지 못하는 오리, 날 수 있고 꽥꽥 우는 오리, 날 수 있고 울지 못하는 오리을 만들 수 있다.
어떻게 이런게 가능할까?
바로 컴포지션 때문이다. 컴포지션은 다른 클래스 혹은 인터페이스를 상속하는 것이 아닌 필드에 포함시키는 것으로 상속과 유사하게 포함된 클래스 혹은 인터페이스의 기능들을 가져 올 수 있게 하기 때문이다.
어떨 때 이 패턴을 사용하면 좋을까?
어떤 객체를 만들고자 하는데.. 기능을 다른 곳으로 따로 빼서 관리하고 싶고 원하는 기능들을 갈아끼고 싶을 때 사용하면 좋다.
예를 들어 게임을 만든다고 해보자.
케릭터를 만들 것인데.. 이 케릭터(Character)가 장착할 수 있는 무기(Weapon)은 다양한 종류가 있을 수 있다.
검, 둔기, 마법봉, 표창, 단검, 활 등등 이런 모든 무기를 Character 클래스 안에 구현할 것인가??
그렇게 하면 코드를 이해하기도 수정하기도 너무 까다로울 것이다.
그래서 각자 무기에 맞는 기능들을 Weapon이라는 인터페이스를 상속받는 각자 클래스로 위임시키고그 클래스들을 구현하는 것으로 케릭터와 무기와 관련된 코드를 떨어뜨려 놓을 수 있다.떨어뜨려 놓는다고 손해 보는 것도 없다. Weapon 인터페이스를 Character 클래스에 추가하면 Weapon을상속 받아 만들어진 모든 기능들을 사용할 수 있기 때문이다.
좀 더 깊은 이해를 위해..
지금까지 설명을 들어온 사람 중에 뭔가 이상하다는 것을 느낀 사람이 있을지 모른다.
(특히 C++에 익숙한 사람이라면 더욱 그럴것이다.)
Duck의 필드의 타입은 FlyBehavior 혹은 QuackBehavior인데 어떻게 그 범위를 벗어나는 FlyWithWings, FlyNoWay, Quack, NoQuack 만큼 메모리가 접근 가능하지..??
왼쪽은 FlyWithWings의 인스턴스로 만든것을 보여주고 있다.
그런데 그 인스턴스를 FlyBehavior타입으로 받으면 어떻게 될까?
FlyBehavior flyBehavior = new FlyWithWings();
flyBehavior.fly();
그리고 나서 fly() 메소드를 호출하면 어떻가 될까? FlyBehvaior 타입이 접근 할 수 있는 메모리 영역만큼만 접근 해야 하지 않을까?? 그래서 검은색 영역의 fly() 메소드가 호출되어야 하는 것 아닐까?
C++에서는 실제로 그렇다.
FlyBehavior* flyBehavior = new FlyWithWings();
flyBehavior->fly();
똑같이 이런 코드를 실행하면 FlyBehavior 클래스가 가지고 있는 fly가 호출된다. (당연히 예제에서는 추상클래스 결과는 오류가 날것이다.)
즉 C++에서 포인터로 딱 찝은 영역(위의 그림에선 검은색)만큼만 접근이 가능하다. 따라서 실제 인스턴스가 FlyWithWings로 만들어 졌다고 하더라도 FlyBehavior 포인터로 인스턴스를 다루면 FlyBehavior의 기능들만 사용하게 된다.
하지만 자바에서는 다르다. 자바에는 동적 바인딩이라는 개념이 있다.
현재 인스턴스를 다루고 있는 타입이 부모 타입(FlyBehavior)이더라도 인스턴스가 FlyWithWings로 만들어져있고 오버라이드 된 메소드가 있다면 부모의 메소드를 호출하여도 자식의 메소드를 호출하게 된다.
즉 타입이 가지고 있는 메모리 영역 밖에 있는 메모리에 접근하게 한다.
자바에서는 동적 바인딩이 default이다. 그래서 타입과 무관하게 자식을 다룰 수 있다.
그럼 C++에는 없냐? 물론 있다. 동적으로 바인딩할 메소드에 virtual 라는 키워드를 붙이면 된다.
자세한 설명은 여기보면 좋을 것 같다.
이렇게 동적 바인딩을 통해서 컴파일 시점 이후로 프로그램이 동작하는 그 순간 객체가 어떤 형태인지 파악해서 동작을 수행하게 하면 좀 더 유연한 형태의 코드를 만들 수 있다.
그리고 반대로 컴파일 시점 이전에 어떤 동작을 미리 정해두는 것을 정적 바인딩이라고 한다.
오늘 주제인 스트래티지 패턴 또한 동적 바인딩이 있기에 Duck 인스턴스가 FlyBehavior라는 타입을 가지고도 FlyBehavior를 상속받아 만들어진 클래스들(FlyWithWings, FlyNoWay)의 인스턴스를 다룰 수 있게 되는 것이다.