Strategy Pattern

Darren

Context

為了達到相同的目的,

物件可以因地制宜,

讓行為擁有多種不同的實作方法。

 

Example

一個壓縮檔案物件,

可以採用zip、arj、rar、tar、7z...

不同的演算法來執行壓縮工作

Problem Statement

如何讓物件自由切換演算法

或行為實作?

Intuition solution

把所有的演算法全部寫進同一個物件,

然後用條件式判斷來選用所要執行的版本

Drawbacks

Drawback1

不好理解與修改

物件的程式碼很容易變得過於肥大

Drawback2

不會使用到全部的演算法

物件占用過多的記憶體空間

Drawback3

必須要修改既有的程式碼

擴充新的演算法有點麻煩

Drawback4

不容易分別開發、修改與測試每一個演算法。

Solution

Solution

public interface Strategy {
   public int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}
public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}
public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());		
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());		
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationMultiply());		
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

Q&A

State Pattern

Darren

Contex

一個物件的行為

根據物件自身狀態不同

而表現出不同的反應動作

Problem Statement

當物件內部狀態改變時,

如何讓它的行為也跟著改變

Drawbacks

  1. 物件的程式碼很容易變得太長,
    不好理解與修改
  2. 物件容易存在許多與狀態相關的重複程式碼。
  3. 單一物件狀態太多,變得不易測試。

Solution

Problem Statement

Playable Character

Idle

Walk

Jump

class Player
	Update()
		if InputRight 
			moveRight()
			animWalkingRight()
		else if InputLeft
			moveLeft()
			animWalkingLeft()
		else 
			animIdle()

Walk

class Player
    Update()
        if InputRight
            moveRight()
            animWalkingRight()
        else if InputLeft
            moveLeft()
            animWalkingLeft()
        else if InputUp
            moveUp()
            animJumping()
        else
            animIdle()

Jump

Ilimited Jump ?

Constrained Jump

Standing State > Jumping State

Jumping State > Jumping State

class Player
    enum states{ Standing, Jumping }
    currentState = states.Standing;
    Update()
        if InputRight
            moveRight()
            if currentState == states.Standing
                animWalkingRight()
            else animJumping()
        else if InputLeft
            moveLeft()
            if currentState == states.Standing
                animWalkingLeft()
            else animJumping()
        else if InputUp && currentState == states.Standing
            moveUp()
            animJumping()
        else
            animIdle()
    onGroundHit()
        currentState = states.Standing;
    onGroundLeave()
        currentState = states.Jumping;

Jump

class Player
    enum states{ Standing, Jumping, Dead }
    currentState = states.Standing;
    Update()
        if currentState != states.Dead
            if InputRight
                moveRight()
                if currentState == states.Standing
                    animWalkingRight()
                else animJumping()
            else if InputLeft
                moveLeft()
                if currentState == states.Standing
                    animWalkingLeft()
                else animJumping()
            else if InputUp && currentState == states.Standing
                moveUp()
                animJumping()
            else
                animIdle()
    onGroundHit()
        currentState = states.Standing
    onGroundLeave()
        currentState = states.Jumping
    onHit()
        animDead()
        currentState = states.Dead

Dead

class Player
    enum states{ Standing, Jumping, Dead, Crouching }
    currentState = states.Standing;
    Update()
        if currentState != Dead
            if InputRight
                moveRight()
                if currentState == states.Standing
                    animWalkingRight()
                if currentState == states.Crouching
                    animWalkingCrouchedRight()
                else animJumping()
            else if InputLeft
                moveLeft()
                if currentState == states.Standing
                    animWalkingLeft()
                if currentState == states.Crouching
                    animWalkingCrouchedLeft()
                else animJumping()
            else if InputUp &&
                    ( currentState == states.Standing || currentState == states.Crouching )
                moveUp()
                animJumping()
            else if InputDown
                if currentState == states.Standing
                    currentState = states.Crouching                    
                if currentState == states.Crouching
                    animCrouching()
            else
                animIdle()
    onGroundHit()
        currentState = states.Standing;
    onGroundLeave()
        currentState = states.Jumping;
    onHit()
        currentState = states.Dead;
        

What if I press UP and DOWN at the same time?

This is becoming so messy and we're not even halfway !

State Design Pattern

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

Behavior

States

  • Standing
  • Jumping
  • Dead
  • Crouching

Intent

Standing

Dead

Crouching

Jumping

Player

PlayerState

PlayerJumping

PlayerStanding

public interface IPlayerState{
	void Up();
	void Down();
	void Left();
	void Right();
	void UpRight();
	void UpLeft();
	void DownRight();
	void DownLeft();
	void None();
	string GetAnimationName ();
}

PlayerState

PlayerIdle - IdleState

public class PlayerIdle : IPlayerState{
	...
        public static IPlayerState GetInstance(Player player){...}
	public override void Right(){
		player.MoveRight ();
		player.SetState ( PlayerWalking.GetInstance(player) );
	}
	public override void Left(){...}
	public override void Up(){
		player.Jump ();
		player.SetState ( PlayerJumping.GetInstance(player) );
	}
	public override void UpRight(){...}
	public override void UpLeft(){...}
	public override void Down(){
		player.SetState ( PlayerCrouched.GetInstance(player) );
	}
	public override void DownRight(){
		player.MoveRight ();
		player.SetState ( PlayerWalkingCrouched.GetInstance(player) );
	}
	public override void DownLeft(){...}
	public override void None(){}
}
public class Player : MonoBehaviour {
	private IPlayerState currentState ;
	...
	void FixedUpdate () {
		bool up = Input.GetKey (KeyCode.UpArrow);
		bool down = Input.GetKey (KeyCode.DownArrow);
		bool left = Input.GetKey (KeyCode.LeftArrow);
		bool right = Input.GetKey (KeyCode.RightArrow);

		if ( up && !down && !left && !right ) currentState.Up();
		else if ( up && !down && left && !right ) currentState.UpLeft();
		else if ( up && !down && !left && right ) currentState.UpRight();
		else if ( !up && down && !left && !right ) currentState.Down();
		else if ( !up && down && left && !right ) currentState.DownLeft();
		else if ( !up && down && !left && right ) currentState.DownRight();
		else if ( !up && !down && !left && right ) currentState.Right();
		else if ( !up && !down && left && !right ) currentState.Left();
		else currentState.None(); 
	}
	public void SetState(IPlayerState newState){
		if (currentState != newState) {
			currentState = newState;
			...
		}
	}
	void Update(){
		...
			animator.SetTrigger( currentState.GetAnimationName() );
		...
	}
	public void Jump(){...}
	public void MoveRight(){...}
	public void MoveLeft(){...}
	...
}

Player

Strategy vs State

State - Design Patterns

By chiao

State - Design Patterns

  • 229