北川广海の梦

北川广海の梦

设计模式:状态模式与策略模式

2021-11-03

状态模式

在程序中,我们通常可以认为,对象在任一时刻的状态都是有限,可枚举的。且根据不同的状态,对象的行为也是不同的。对象可以瞬间从一个状态切换到另一个状态。

举一个例子,一个音乐播放器,具有以下三种状态:
初始化、播放、暂停、加载中

在播放的时候,我们可以按下暂停键,此时播放器的状态会转移到暂停。
在暂停的时候,按下播放键,此时会转移到播放状态。
而无论在播放还是暂停,如果按下“下一曲”按钮,一定会进入加载中状态,当加载中结束后,播放器又会自动变为播放状态。

传统实现方式,我们会这样写代码:

 public enum State
    {
        Init = 0,
        Pause = 1,
        Play = 2,
        Loading = 3,
    }

    public class Player
    {
        private State _state;

        public Player()
        {
            _state = State.Init;
        }


        public void PlayButton()
        {
            if (_state == State.Init)
            {
                Load();
                Play();
            }
            else if (_state == State.Pause)
            {
                Play();
            }
        }

        public void PauseButton()
        {
            if (_state == State.Play)
            {
                Pause();
            }
        }

        public void NextButton()
        {
            Load();
            Play();
        }

        private void Load()
        {
            _state = State.Loading;
            Thread.Sleep(1000);
        }

        private void Play()
        {
            _state = State.Play;
        }

        private void Pause()
        {
            _state = State.Pause;
        }
    }

可以看到,在按下不同的按钮的时候,我们需要根据当前的状态进行不同的处理,目前播放器的状态只有四种,如果状态更多一些,比如再增加一种“单曲循环状态”、“FM模式播放状态”,那么就会对代码产生侵入式的影响,我们需要和旧有的if-else产生大量的纠缠与斗争。

而通过状态模式,可以优雅的解决这个问题。
下面将用状态模式重新实现代码:

  public interface IPlayerState
    {
        void SetPlayer(IPlayer player);

        void OnNextButton();

        void OnPlayButton();

        void OnPauseButton();
    }

    public class InitState : IPlayerState
    {
        private IPlayer _player;

        public void SetPlayer(IPlayer player)
        {
            _player = player;
        }

        public void OnNextButton()
        {
            if (_player == null)
                throw new InvalidOperationException();
            var loadingState = new LoadingState();
            loadingState.SetPlayer(_player);
            _player.ChangeState(loadingState);
            _player.Loading();
            _player.Play();
            var playState = new PlayingState();
            playState.SetPlayer(_player);
            _player.ChangeState(playState);
        }

        public void OnPlayButton()
        {
            throw new System.NotImplementedException();
        }

        public void OnPauseButton()
        {
            throw new System.NotImplementedException();
        }
    }

    public class PlayingState : IPlayerState
    {
        public void SetPlayer(IPlayer player)
        {
            throw new NotImplementedException();
        }

        public void OnNextButton()
        {
            throw new NotImplementedException();
        }

        public void OnPlayButton()
        {
            throw new NotImplementedException();
        }

        public void OnPauseButton()
        {
            throw new NotImplementedException();
        }
    }

    public class PauseState : IPlayerState
    {
        public void SetPlayer(IPlayer player)
        {
            throw new NotImplementedException();
        }

        public void OnNextButton()
        {
            throw new NotImplementedException();
        }

        public void OnPlayButton()
        {
            throw new NotImplementedException();
        }

        public void OnPauseButton()
        {
            throw new NotImplementedException();
        }
    }

    public class LoadingState : IPlayerState
    {
        public void SetPlayer(IPlayer player)
        {
            throw new NotImplementedException();
        }

        public void OnNextButton()
        {
            throw new NotImplementedException();
        }

        public void OnPlayButton()
        {
            throw new NotImplementedException();
        }

        public void OnPauseButton()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPlayer
    {
        void Play();

        void Pause();

        void Loading();

        void ChangeState(IPlayerState state);
    }

    public class Player : IPlayer
    {
        private IPlayerState _state;

        public Player(IPlayerState initState)
        {
            _state = initState;
        }

        public void PlayButton()
        {
            _state.OnPlayButton();
        }

        public void PauseButton()
        {
            _state.OnPauseButton();
        }

        public void NextButton()
        {
            _state.OnNextButton();
        }

        public void Play()
        {
        }

        public void Pause()
        {
        }

        public void Loading()
        {
        }

        public void ChangeState(IPlayerState state)
        {
            _state = state;
        }
    }

由于篇幅过长,我仅实现了在初始化状态下,按下下一曲的状态改变。可以看到,我们完全干掉了if-else,但是代价确实增加了足足四个状态类,每个状态类实现了三个方法。代码量相比if-else写法增加了很多。但是这样做是完全值得的,它的可维护性,是if-else无可比拟的。

策略模式

策略模式允许我们将一系列算法进行封装,客户端可以自由选择所需的算法,而无需了解算法的细节。

例如最经典的排序算法。我们可以将排序算法抽象成一个接口,它输入一个无序数组,返回一个有序数组。我们可以使用各种排序方法实现这个接口,例如冒泡,快排等等。

策略模式的例子较为简单,就不再通过代码演示。

不过值得注意的是,许多语言支持函数对象(函数指针),可以通过函数对象来直接指向特定的算法,而无需过多的定义接口和类。例如C#完全可以通过委托来进行实现。

总结

状态模式与策略模式具有一定的相似之处,他们都将工作委托给了另一对象。但是在状态模式中,几乎每种状态类都知道其他状态的存在,并且会在合适的时候使状态发生转换。而策略模式中,每种策略之间不知道彼此的存在。他们之间没有依赖关系。