8. Strategy Pattern ( 스트래티지 / 전략 패턴 C# )

|

[읽기전에]

  UML 다이어그램 : UML클래스 다이어그램 기본상식 http://hongjinhyeon.tistory.com/25 

  포스팅되는 디자인 패턴의 예는 스타크래프트를 기본으로 하였습니다 : 디자인 패턴을 시작하며 http://hongjinhyeon.tistory.com/24


<기본 정의 >


1. Strategy Pattern 정의

  

  -알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다. 스트래티지를 활용하면 알고리즘을 사용하는 클라이언트

   와는 독립적으로 알고리즘을 변경할 수 있다.

   (Define a family of algorithms, encapsulate each one, and make them interchangeable. 

    Strategy lets the algorithm vary independently from clients that use it.)

  


2. UML Diagram




  -Context : Strategy의 구상객체를 소유하며 해당 구상객체의 메소드를 실행하는 메소드를 가지고 있다(ContextInterface)

  -Strategy : 알고리즘의 추상 객체 , 여러 타입의 구상객체들의 추상 객체가 된다.

  -ConcreteStrategyA~C : Context에 적용될 각각 다른 알고리즘으로 되있는 메소드들을 소유함



3. 사용용도 및 장점


 -상속으로 해결될 수 없는 코드 중복이나 객체의 실시간 알고리즘의 변경시에 유용하다.

 -Strategy 추상객체를 상속해서 신규 알고리즘을 추가하기가 용이하다. ( 확장성 )

 -추후에 알고리즘이 변경시에도 해당 알고리즘만 변경되면 된다.(클라이언트와 독립적으로 변경 )


4. 소스


using System;
 
namespace DoFactory.GangOfFour.Strategy.Structural
{
  /// <summary>
  /// MainApp startup class for Structural
  /// Strategy Design Pattern.
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Entry point into console application.
    /// </summary>
    static void Main()
    {
      Context context;
 
      // Three contexts following different strategies
      context = new Context(new ConcreteStrategyA());
      context.ContextInterface();
 
      context = new Context(new ConcreteStrategyB());
      context.ContextInterface();
 
      context = new Context(new ConcreteStrategyC());
      context.ContextInterface();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// <summary>
  /// The 'Strategy' abstract class
  /// </summary>
  abstract class Strategy
  {
    public abstract void AlgorithmInterface();
  }
 
  /// <summary>
  /// A 'ConcreteStrategy' class
  /// </summary>
  class ConcreteStrategyA : Strategy
  {
    public override void AlgorithmInterface()
    {
      Console.WriteLine(
        "Called ConcreteStrategyA.AlgorithmInterface()");
    }
  }
 
  /// <summary>
  /// A 'ConcreteStrategy' class
  /// </summary>
  class ConcreteStrategyB : Strategy
  {
    public override void AlgorithmInterface()
    {
      Console.WriteLine(
        "Called ConcreteStrategyB.AlgorithmInterface()");
    }
  }
 
  /// <summary>
  /// A 'ConcreteStrategy' class
  /// </summary>
  class ConcreteStrategyC : Strategy
  {
    public override void AlgorithmInterface()
    {
      Console.WriteLine(
        "Called ConcreteStrategyC.AlgorithmInterface()");
    }
  }
 
  /// <summary>
  /// The 'Context' class
  /// </summary>
  class Context
  {
    private Strategy _strategy;
 
    // Constructor
    public Context(Strategy strategy)
    {
      this._strategy = strategy;
    }
 
    public void ContextInterface()
    {
      _strategy.AlgorithmInterface();
    }
  }
} 


5. 실행결과


Called ConcreteStrategyA.AlgorithmInterface()

Called ConcreteStrategyB.AlgorithmInterface()

Called ConcreteStrategyC.AlgorithmInterface()




< 실제 적용 >


1. UML Diagram






2. 사용용도 및 장점


 -테란의 유닛중에 마린(지상유닛, 공격가능)과 메딕(지상유닛, 공격불가), 레이쓰(공중유닛, 공격가능) 있습니다.  이 객체의 행동에는

  이동(Move)과 공격(Attack)이 존재합니다. Unit이라는 클래스에 Move와 Attack 메소드가 있으면, 상속받는 객체에서 각각 메소드들에

  대한 구체적인 내용을 처리하게 됩니다. 그런데 마린과 레이쓰는 공격이 가능하지만 메딕을 공격이 불가능하기 때문에 Attack에 빈 함수로

  처리가 될것입니다. 


  이렇게 상위 클래스에서 공통의 기능을 상속받아서 오버라이딩할때의 문제점은 Unit을 상속받는 모든 객체에서 의미없는 빈 메소드들을 생성

  해야하며, Unit에 새로운 기능이 생성되었을때에 상속받는 객체들을 모두 수정해주어야하는 문제점이 발생합니다.


  이때 전략패턴을 사용하게되면 불필요한 코드생성을 없앨수 있으며 기능확장에 유연하게 됩니다. 

  Unit 객체에 Moveable이라는 클래스와 Attackable이라는 클래스를 가지고 있어서 마린이나 메딕의 구상객체에서 해당 유닛에 해당하는

  이동이나 공격에 대한 알맞은 기능을 매칭시켜주면 됩니다.


  마린은 Attack을 메딕은 NoAttack을 레이쓰는 Attack으로 설정해주면 Unit클래스에서 Attack을 실행하게되면 설정된 내용들이 처리가 됩니다. 

  만약 메딕이 확장팩에서 업그레이드가 되서 특수한 공격으로 공격이 가능하게 되면 Attackable을 상속받아서 SpecialAttack이라는 

  구상클래스를 생성해주고 메딕이 생성될때 설정해주면 됩니다.



3. 소스


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Strategy
{
    class Program
    {
        static void Main(string[] args)
        {
            //마린
            Unit unit = new Marine(new MoveLand(), new Attack());
            unit.Move();
            unit.Attack();

            //메딕
            unit = new Medic(new MoveLand(), new NoAttack());
            unit.Move();
            unit.Attack();

            //레이쓰
            unit = new Wrath(new MoveSky(), new Attack());
            unit.Move();
            unit.Attack();

            //메딕에게 스페샬!공격 기능으로 변경!!
            unit = new Medic(new MoveLand(), new SpecialAttack());
            unit.Move();
            unit.Attack();

            Console.ReadKey();
        }

       
        abstract class Moveable
        {
            public abstract void Move();
        }
              
        class MoveLand : Moveable
        {
            public override void Move()
            {
                Console.WriteLine(
                  "Move Land");
            }
        }
        
        class MoveSky : Moveable
        {
            public override void Move()
            {
                Console.WriteLine(
                  "Move Sky");
            }
        }

        abstract class Attackable
        {
            public abstract void AttackEnemy();
        }

        class Attack : Attackable
        {
            public override void AttackEnemy()
            {
                Console.WriteLine(
                  "Attack Enemy!");
            }
        }

        class NoAttack : Attackable
        {
            public override void AttackEnemy()
            {
                Console.WriteLine(
                  "Can not Attack");
            }
        }

        /// <summary>
        /// 메딕에게 신규 공격기술 추가
        /// </summary>
        class SpecialAttack : Attackable
        {
            public override void AttackEnemy()
            {
                Console.WriteLine(
                  "Medic can Special Attack!!");
            }
        }
       

        class Unit
        {
            private Moveable moveAble;
            private Attackable attackAble;

            // Constructor
            public Unit(Moveable moveable, Attackable attackable)
            {
                this.moveAble = moveable;
                this.attackAble = attackable;
            }

            public void Attack()
            {
                attackAble.AttackEnemy();
            }

            public void Move()
            {
                moveAble.Move();
            }

            public Moveable MoveAble
            {
                set { this.moveAble = value; }
            }

        }

        class Marine : Unit
        {
            public Marine(Moveable moveable, Attackable attackable)
                : base(moveable, attackable)
            {

            }
        }

        class Medic : Unit
        {
            public Medic(Moveable moveable, Attackable attackable)
                : base(moveable, attackable)
            {

            }
        }

        class Wrath : Unit
        {
            public Wrath(Moveable moveable, Attackable attackable)
                : base(moveable, attackable)
            {

            }
        }
    }
}


4. 실행결과





5. 피드백


 -전략패턴도 다른 패턴처럼 자주사용하는 패턴중에 하나같다.

 -데코레이터와는 비슷한듯 많이 다른듯하다. 데코레이터도 동적으로 기능을 할당하지만 그 원래의 기능을 존재하고 새로운 기능이

   추가되는 형식인데, 전략패턴은 기능이 대체가된다.


 ※와이프가 열심히 포스팅하는 것보면서 하는말 "그렇게 공유하면 자기 밑천 드러나는 거아냐? 다른사람도 그럼 다 알게되잖아..." (ㄱ.-)

    저는 말합니다. "네 그렇습니다. 공부 더 열심히 하겠습니다. " ㅜㅜ 그래도 공유는 좋은거니까요

 

   


[참조]

 -http://www.dofactory.com

 -Head First Design Patterns

And