[내일배움캠프 TIL] 디자인 패턴 - 전략 패턴

KYJ의 Tech Velog·2023년 9월 25일
0

TIL

목록 보기
37/71
post-thumbnail

Today I Learned

오늘은 디자인 패턴, 그 중에서도 전략 패턴에 대해 이야기해보도록 하겠습니다.

Today I Done

디자인 패턴

소프트웨어를 설계할 때 특정한 상황에서 자주 발생하는 문제들을 해결하기 위한 방법을 디자인 패턴이라고 합니다.

정말 다양한 디자인 패턴이 존재하는데 그 중에서도 이번에는 '전략 패턴'에 대해 알아보려고 합니다.

전략 패턴

특정한 작업을 독립적으로 정의하고 캡슐화하여 해당 작업을 동적으로 교체할 수 있도록 하는 패턴입니다.

특정한 작업이란 어떠한 클래스틀 통해 하위 클래스를 구현해서 객체를 생성할 때, 하위 클래스가 여러 개라면 그 클래스마다 차이점이 생길 것입니다. 그 차이점을 특정한 작업이라고 할 수 있습니다. 그것을 분리해내는 것을 캡슐화라고 합니다.

다양한 오리를 구현해야 한다고 가정하겠습니다. 여러가지 오리란 무생물의 오리도 포함되어 있습니다.

이 때 오리의 행동에 날다라는 행동을 추가한다고 한다면 다음과 같이 구현해야 합니다.

하지만 RubberDuck의 경우 실제로 날 수가 없는 오리입니다. 그렇다면 RubberDuck의 fly() 함수만 아무 동작도 하지 않도록 재정의해야 하죠.

만약 RubberDuck처럼 날 수 없는 오리들이 계속 추가되야 한다면 계속 오버라이드해서 재정의해주어야 합니다. quack() 함수에서도 문제가 생길 수 있겠죠.

상속의 사용 이유는 코드의 재사용을 피하기 위함이었는데 상황에 따라서 피할 수 없음을 알 수 있습니다. 모든 서브 클래스의 동작을 추정하기는 쉽지 않죠. 어떤 서브 클래스를 추가해야 될지 전부 미리 파악할 수는 없습니다.

이 때문에 우리는 상위 클래스에서 하위 클래스로 나누어질 때, 달라지는 부분과 달라지지 않는 부분을 분리해야 합니다. 이를 캡슐화라고 합니다.

오리이 대해 행동을 분류해보면 다음 사진과 같습니다.

우는 행동이나 나는 행동은 Duck 클래스에서 분리하는 것이 좋겠죠. 이를 구현하기 위해 Interface를 활용합니다.

다음 사진처럼 각각의 인터페이스를 Duck 클래스에서 구현하는 것이 아닌 특정 행동만을 목적으로 하는 클래스에서 구현합니다.

// 행동 인터페이스
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 Quack : IQuackBehavior
{
    public void Quack()
    {
        Console.WriteLine("꽥꽥 소리를 냅니다.");
    }
}

// 삑삑 소리를 내는 행동 구현
public class Squeak : 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 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.PerformFly();
        rubberDuck.PerformQuack();
    }
}

이렇게 구현한다면 실제 실행할 때 사용되는 객체(mallardDuck, rubberDuck)가 상위 형식(Duck)으로 만들어질 수 있습니다.

Main 함수에서는 객체들의 구체적인 행동은 알 수 없습니다. 그저 Duck 클래스의 메서드들을 호출할 뿐입니다.

이렇게 구현한다면 새로운 객체를 추가할 때 편리합니다. 나무 오리를 새로 만든다고 가정해봅시다.

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.PerformQuack();
    }
}

이 과정에서 나무 오리는 Duck 클래스의 영향도 받지 않았고 기존 코드를 오버라이드 할 필요가 없었습니다. 그저 새로운 나무 오리에 대한 것들을 새로 추가하는 것만으로 구현할 수 있는 것입니다.

이렇게 전략 패턴을 활용할 수 있다면 앞서 설명드렸던 상속으로 발생하는 코드의 불필요한 재사용이나 예기치 못한 문제들을 피할 수 있게 됩니다.

Tomorrow's Goal

  • 팀 프로젝트 필수 요구사항 구현
  • 코드 카타 Clear
  • 알고리즘 문제 풀이 공유

0개의 댓글

Powered by GraphCDN, the GraphQL CDN