Android 状态模式

源码地址

介绍

状态模式中的行为是由状态来决定的,不同的状态有不同的行为。状态模式和策略模式的结构几乎一样,但它们的目的、本质完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。

用一句话来表述,状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

使用场景

  1. 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  2. 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的多分支语句(if-else 或 switch-case),且这些分支依赖于该对象的状态。

简单示例

需求:电视遥控器的操作。电视遥控器,含有开机、关机、下一频道、上一频道、调高音量、调低音量这几个功能。

第一版实现:

public class TvController {
    //开机状态
    private static final int POWER_ON = 1;
    //关机状态
    private static final int POWER_OFF = 2;

    private int mState = POWER_OFF;

    public void powerOn() {
        if (mState == POWER_OFF) {
            mState = POWER_ON;
            System.out.println("开机啦");
        }
    }

    public void powerOff() {
        if (mState == POWER_ON) {
            mState = POWER_OFF;
            System.out.println("关机啦");
        }
    }

    public void nextChannel() {
        if (mState == POWER_ON) {
            System.out.println("下一频道");
        } else {
            System.out.println("两个红灯提示没有开机");
        }
    }

    public void preChannel() {
        if (mState == POWER_ON) {
            System.out.println("上一频道");
        } else {
            System.out.println("两个红灯提示没有开机");
        }
    }

    public void turnUp() {
        if (mState == POWER_ON) {
            System.out.println("调高音量");
        } else {
            System.out.println("两个红灯提示没有开机");
        }
    }

    public void turnDown() {
        if (mState == POWER_ON) {
            System.out.println("调低音量");
        } else {
            System.out.println("两个红灯提示没有开机");
        }
    }
}

上面各个操作都需要判断有没有开机,出现大量if-else语句,代码重复、操作较为混乱。如果新增几个状态,哪个这种情况会变得更为严重,代码的可维护性变差。

下面是状态模式的实现:

  1. 电视状态接口,定义了电视操作的函数

    public interface TvState {
        void nextChannel();
        void preChannel();
        void turnUp();
        void turnDown();
    }
    
  2. 关机状态,此时只有开机功能时有效的

    public class PowerOffState implements TvState {
        @Override
        public void nextChannel() {
        }
    
        @Override
        public void preChannel() {
        }
    
        @Override
        public void turnUp() {
        }
    
        @Override
        public void turnDown() {
        }
    }
    
  3. 开机状态,此时再触发开机功能不做任何操作

    public class PowerOnState implements TvState {
        @Override
        public void nextChannel() {
            System.out.println("下一频道");
        }
    
        @Override
        public void preChannel() {
            System.out.println("上一频道");
        }
    
        @Override
        public void turnUp() {
            System.out.println("调高音量");
        }
    
        @Override
        public void turnDown() {
            System.out.println("调低音量");
        }
    }
    
  4. 电源操作接口

    public interface PowerController {
        void powerOn();
        void powerOff();
    }
    
  5. 电视遥控器,类似于经典状态模式中的Context

    public class TvController implements PowerController {
        private TvState mTvState;
    
        public void setTvState(TvState tvState) {
            mTvState = tvState;
        }
    
        @Override
        public void powerOn() {
            setTvState(new PowerOnState());
            System.out.println("开机啦");
        }
    
        @Override
        public void powerOff() {
            setTvState(new PowerOffState());
            System.out.println("关机啦");
        }
    
        public void nextChannel() {
            mTvState.nextChannel();
        }
    
        public void preChannel() {
            mTvState.preChannel();
        }
    
        public void turnUp() {
            mTvState.turnUp();
        }
    
        public void turnDown() {
            mTvState.turnDown();
        }
    }
    
  6. 客户端调用

    public class Client {
        public static void main(String[] args) {
            TvController tvController = new TvController();
            //设置开机状态
            tvController.powerOn();
            //下一频道
            tvController.nextChannel();
            //调高音量
            tvController.turnUp();
            //设置关机状态
            tvController.powerOff();
            //调高音量
            tvController.turnUp();
        }
    }
    
  7. 输入结果

    开机啦
    下一频道
    调高音量
    关机啦
    

上面代码中,抽象了一个TvState接口,两个实现类,开机状态(PowerOnState)和关机状态(PowerOffState)。开机状态下只有开机功能是无效的;而在关机状态下,只有开机功能是可用的,其他功能都不会生效。

Android源码中的实现

WiFi 状态管理。

状态模式实战

需求:登录系统。例如,用户的默认状态为未登录状态,在 MainActivity 界面点击转发时会先跳转到登录页面,登录成功回到 MainActivity,此时再转发就能实现真正的转发功能。

  1. 用户状态

    public interface UserState {
        /**
         * 转发
         */
        void forward(Context context);
    
        /**
         * 评论
         */
        void comment(Context context);
    }
    
  2. 已登录状态

    public class LoginState implements UserState {
        @Override
        public void forward(Context context) {
            Toast.makeText(context, "转发微博", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void comment(Context context) {
            Toast.makeText(context, "评论微博", Toast.LENGTH_SHORT).show();
        }
    }
    
  3. 注销状态,即未登录状态

    public class LogoutState implements UserState {
        @Override
        public void forward(Context context) {
            gotoLoginActivity(context);
        }
    
        @Override
        public void comment(Context context) {
            gotoLoginActivity(context);
        }
    
        private void gotoLoginActivity(Context context) {
            Intent intent = new Intent(context, LoginActivity.class);
            context.startActivity(intent);
        }
    }
    
  4. 用户接口和状态管理类

    public class LoginContext {
        //用户状态,默认为未登录状态
        UserState mState = new LogoutState();
        //单例
        static LoginContext sLoginContext = new LoginContext();
    
        private LoginContext() { }
    
        public static LoginContext getLoginContext() {
            return sLoginContext;
        }
    
        public void setState(UserState state) {
            mState = state;
        }
        //转发
        public void forward(Context context) {
            mState.forward(context);
        }
        //评论
        public void comment(Context context) {
            mState.comment(context);
        }
    }
    
  5. 主界面

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initListener();
        }
    
        private void initListener() {
            findViewById(R.id.btn_forward).setOnClickListener(this);
            findViewById(R.id.btn_logout).setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_forward:
                    //调用LoginContext的转发函数
                    LoginContext.getLoginContext().forward(this);
                    break;
                case R.id.btn_logout:
                    //设置为注销状态
                    LoginContext.getLoginContext().setState(new LogoutState());
                    Toast.makeText(this, "注销成功", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }
    
  6. 登录Activity

    public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    
        private EditText mUsername;
        private EditText mPassword;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
    
            initView();
            findViewById(R.id.login).setOnClickListener(this);
        }
    
        private void initView() {
            mUsername = findViewById(R.id.username);
            mPassword = findViewById(R.id.password);
        }
    
        @Override
        public void onClick(View v) {
            String username = mUsername.getText().toString().trim();
            String password = mPassword.getText().toString().trim();
            if ("123".equals(username) && "123".equals(password)) {
                //登录成功后修改为已登录状态
                LoginContext.getLoginContext().setState(new LoginState());
                Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
                finish();
            } else {
                Toast.makeText(this, "账号或密码错误", Toast.LENGTH_SHORT).show();
            }
        }
    }
    

源码地址

总结

  • 优点

    状态模式将所有与一个特定状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将繁琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性与可维护性。

  • 缺点

    必然会增加系统类和对象的个数。

推荐阅读更多精彩内容