특정한 작업(알고리즘)을 독립적으로 정의하고 캡슐화 하여, 해당 작업을 동적으로 교체할 수 있도록 하는 패턴을 의미한다.
우리가 다양한 오리를 구현해야 한다고 가정해보자.

우리는 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리해야 한다.
오리의 예제에서 달라지는 부분과 달라지지 않는 부분을 분리하면 아래와 같다.
꽥꽥 우는 행동이나, 나는 행동은 오리 종류에 따라 달라지는 부분으로, Duck 클래스에서 분리해야 한다.
다시 말해, 해당 행동들을 Duck 클래스에서 직접 구현하지 않아야 한다. 그렇다면 해당 행동들은 어떻게 처리해야 할까?

인터페이스(QuackBehavior, FlyBehavior)를 사용하면 된다.
그리고 각각의 인터페이스를 Duck 클래스에서 구현하는 것이 아니라 아닌 특정 행동 만을 목적으로 하는 클래스에서 구현하도록 하자.
아래처럼 quak( ) 메서드와 fly( ) 메서드는 Duck 클래스가 아닌 특정 행동 인터페이스를 구현한 별도의 클래스 안에 존재하게 된다.

1. 행동 인터페이스 정의
// 행동 인터페이스
public interface IFlyBehavior
{
void Fly();
}
public interface IQuackBehavior
{
void Quack();
}
// 날 수 있는 행동 구현
public class FlyWithWings : IFlyBehavior
{
public void Fly()
{
Console.WriteLine("날개로 납니다.");
}
}
// 날지 못하는 행동 구현
public class FlyNoWay : IFlyBehavior
{
public void Fly()
{
Console.WriteLine("날지 못합니다.");
}
}
public class FlyRocketPowered : IFlyBehavior
{
public void Fly()
{
Console.WriteLine("로켓 추진으로 엄청나게 날아갑니다!!!");
}
}
// 꽥꽥 소리를 내는 행동 구현
public class Quack : IQuackBehavior
{
public void Quack()
{
Console.WriteLine("꽥꽥 소리를 냅니다.");
}
}
// 삑삑 소리를 내는 행동 구현
public class Squeak : IQuackBehavior
{
public void Quack()
{
Console.WriteLine("삑삑 소리를 냅니다.");
}
}
public class MuteQuack : IQuackBehavior
{
public void Quack()
{
Console.WriteLine("... (침묵)");
}
}
public abstract class Duck
{
protected IFlyBehavior flyBehavior;
protected IQuackBehavior quackBehavior;
public Duck()
{
}
public abstract void Display();
public void PerformFly()
{
flyBehavior.Fly();
}
public void PerformQuack()
{
quackBehavior.Quack();
}
public void Swim()
{
Console.WriteLine("모든 오리는 물에 뜹니다.");
}
public void SetFlyBehavior(IFlyBehavior fb)
{
flyBehavior = fb;
}
public void SetQuackBehavior(IQuackBehavior qb)
{
quackBehavior = qb;
}
}
public class MallardDuck : Duck
{
public MallardDuck()
{
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
public override void Display()
{
Console.WriteLine("Mallard 오리");
}
}
public class RubberDuck : Duck
{
public RubberDuck()
{
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
public override void Display()
{
Console.WriteLine("Rubber 오리");
}
}
class Program
{
static void Main(string[] args)
{
Duck mallardDuck = new MallardDuck();
Duck rubberDuck = new RubberDuck();
mallardDuck.Display();
mallardDuck.PerformFly();
mallardDuck.PerformQuack();
rubberDuck.Display();
rubberDuck.SetFlyBehavior(new FlyRocketPowered());
rubberDuck.PerformFly();
rubberDuck.PerformQuack();
}
}
여기서 핵심은 실제 실행 시에 사용되는 객체(MallardDuck, RubberDuck)가 상위 형식(Duck)으로 만들어 질 수 있다는 것 이다.
Main 함수에서는 MallardDuck이나 RubberDuck의 구체적인 행동을 알 수 없다.
그냥 Duck 클래스의 PerformFly( ), PerformQuack( ) 메서드를 호출할 뿐이다.
이렇게 구현하면 무엇이 좋은 것일까? 앞선 상황을 다시 생각해보자.
나무 오리를 새롭게 만든다고 하면, 꽥꽥 울 수도, 날 수도 없다.
public class DecoyDuck : Duck
{
public DecoyDuck()
{
flyBehavior = new FlyNoWay(); // 날지 못하는 행동 설정
quackBehavior = new MuteQuack(); // 소리를 내지 않는 행동 설정
}
public override void Display()
{
Console.WriteLine("나무 오리");
}
}
// 소리를 내지 않는 행동 구현
public class MuteQuack : IQuackBehavior
{
public void Quack()
{
Console.WriteLine("소리를 내지 않습니다.");
}
}
class Program
{
static void Main(string[] args)
{
Duck mallardDuck = new MallardDuck();
Duck rubberDuck = new RubberDuck();
Duck decoyDuck = new DecoyDuck();
mallardDuck.Display();
mallardDuck.PerformFly();
mallardDuck.PerformQuack();
rubberDuck.Display();
rubberDuck.PerformFly();
rubberDuck.PerformQuack();
decoyDuck.Display();
decoyDuck.PerformFly();
decoyDuck.SetQuackBehvior(new Squeak ());
decoyDuck.PerformQuack();
}
}
이렇게 달라지는 부분을 캡슐화하고, 동적으로 교체되어 사용할 수 있도록 한다면(전략 패턴을 사용한다면)
상속으로 발생하는 예상치 못한 영향과, 불필요한 코드 작성을 피할 수 있게 된다.
SetFlyBehavior, SetQuackBehavior 등을 통해 실행 중에도 행동을 교체할 수 있다.