设计模式(七) — 状态模式

随遇而安——状态模式

状态模式的定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式和策略模式的结构几乎一样,但是他们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

状态模式的使用场景

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

状态模式的实例

下面我们来看下第一个实例:电视遥控器
电视的状态简单分为开机状态和关机状态,在开机状态下可以通过遥控器进行频道切换、音量控制等,重复按开机键是无效的;而在关机状态下,频道切换、调整音量、关机都是无效的操作,只有按开机时会生效。我们先来看看一般的写法。

TvController.class

/**
 * description: 电视遥控器,含有开机、关机、下一个频道、上一个频道、调高音量、调低音量这几个功能
 */
public class TvController {
    private static final String TAG = "TvController";
    //开机状态
    private final static int POWER_ON = 1;
    //关机状态
    private final static int POWER_OFF = 2;
    private int state = POWER_OFF;

    public void powerOn() {
        if (state == POWER_OFF) {
            Log.i(TAG, "开机啦");
        }
        state = POWER_ON;
    }

    public void powerOff() {
        if (state == POWER_ON) {
            Log.i(TAG, "关机啦");
        }
        state = POWER_OFF;
    }

    public void nextChannel() {
        if (state == POWER_ON) {
            Log.i(TAG, "下一个频道");
        } else {
            Log.i(TAG, "没有开机");
        }
    }

    public void prevChannel() {
        if (state == POWER_ON) {
            Log.i(TAG, "上一个频道");
        } else {
            Log.i(TAG, "没有开机");
        }
    }

    public void turnUp() {
        if (state == POWER_ON) {
            Log.i(TAG, "调高音量");
        } else {
            Log.i(TAG, "没有开机");
        }
    }

    public void turnDown() {
        if (state == POWER_ON) {
            Log.i(TAG, "调低音量");
        } else {
            Log.i(TAG, "没有开机");
        }
    }
}

可以看到,在TvController类中,state字段存储了电视的状态,并且在各个操作中根据状态来判断是否应该执行,这就导致了在每个功能中都需要使用if-else。如果状态变为5个,功能变为10个,那就会晕死在判断里吧。那么接下来用状态模式来重构一下。

状态接口
TvState.class

public interface TvState {
    public void nextChannel();

    public void prevChannel();

    public void turnUp();

    public void turnDown();
}

具体状态类
PowerOffState.class

/**
 * description: 关机状态,此时只有开机功能是有效的
 */
public class PowerOffState implements TvState {
    @Override
    public void nextChannel() {

    }

    @Override
    public void prevChannel() {

    }

    @Override
    public void turnUp() {

    }

    @Override
    public void turnDown() {

    }
}

PowerOnState.class

/**
 * description: 开机状态,此时再触发开机功能不做任何操作
 */
public class PowerOnState implements TvState {
    private static final String TAG = "PowerOnState";

    @Override
    public void nextChannel() {
        Log.i(TAG, "下一个频道");
    }

    @Override
    public void prevChannel() {
        Log.i(TAG, "上一个频道");

    }

    @Override
    public void turnUp() {
        Log.i(TAG, "调高音量");
    }

    @Override
    public void turnDown() {
        Log.i(TAG, "调低音量");
    }
}

电源操作接口
PowerController.class

public interface PowerController {
    public void powerOn();

    public void powerOff();
}

操作接口,提供给外部调用
TvController.class

public class TvController implements PowerController{
    private static final String TAG = "TvController";
    TvState tvState;

    public void setTvState(TvState tvState) {
        this.tvState = tvState;
    }

    @Override
    public void powerOn() {
        setTvState(new PowerOnState());
        Log.i(TAG, "开机啦");
    }

    @Override
    public void powerOff() {
        setTvState(new PowerOffState());
        Log.i(TAG, "关机啦");
    }

    public void nextChannel() {
        tvState.nextChannel();
    }

    public void prevChannel() {
        tvState.prevChannel();
    }

    public void turnUp() {
        tvState.turnUp();
    }

    public void turnDown() {
        tvState.turnDown();
    }
}

用户调用

public class Client {
    public void test() {
        TvController tvController = new TvController();
        tvController.powerOn();
        tvController.nextChannel();
        tvController.turnUp();
        tvController.powerOff();
        tvController.turnUp();
    }
}

Log日志如下

09-30 23:58:57.248 24287-24287/? I/TvController: 开机啦
09-30 23:58:57.248 24287-24287/? I/PowerOnState: 下一个频道
09-30 23:58:57.248 24287-24287/? I/PowerOnState:  调高音量
09-30 23:58:57.248 24287-24287/? I/TvController: 关机啦

状态模式将这些行为封装到状态类中,在进行操作时将这些功能转发给状态对象,不同的状态有不同的实现,这样就通过了多态的形式去除了重复、杂乱的if-else语句。

接下来再看一个实例
在Android中最常用的状态模式的地方,应当属登陆这一块了。有些app会设计成不登陆也可以进入app使用,但是涉及到使用用户信息的时候就需要先登陆,那这些需要用户信息的地方(比如转发、评论),如果通过用户是否登陆来一个个判断的话,将出现很多if-else,所以这里可以使用状态模式。

假设MainActivity是app的首页,里面有转发、评论和注销的功能,不登陆也可以直接进入到首页,当点击评论或者转发时,没有登录则直接跳转到登陆,登陆了则直接评论或者转发。

用户状态接口
UserState.class

public interface UserState {
    /**
     * description: 转发
     */
    public void forward(Context context);

    /**
     * description: 评论
     */
    public void comment(Context context);
}

具体状态类
LoginedState.class

/**
 * description: 已登录状态
 */
public class LoginedState implements UserState {
    private static final String TAG = "LoginedState";

    @Override
    public void forward(Context context) {
        Log.i(TAG, "转发微博");
    }

    @Override
    public void comment(Context context) {
        Log.i(TAG, "评论微博");
    }
}

LogoutState.class

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);
    }
}

向外界提供调用Context类
LoginContext.class

public class LoginContext {
    private UserState state = new LogoutState();

    private LoginContext() { }

    public static LoginContext getSingleton() {
        return LoginContext.LoginContextHolder.loginContext;
    }

    private static class LoginContextHolder {
        private static final LoginContext loginContext = new LoginContext();
    }

    public void setState(UserState state) {
        this.state = state;
    }

    //转发
    public void forward(Context context) {
        state.forward(context);
    }

    //评论
    public void comment(Context context) {
        state.comment(context);
    }
}

接下来就是具体的逻辑调用了
MainActivity.class

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //转发按钮
        findViewById(R.id.forward_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //调用LoginContext的转发函数
                LoginContext.getSingleton().forward(MainActivity.this);
            }
        });

        //评论按钮
        findViewById(R.id.comment_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //调用LoginContext的评论函数
                LoginContext.getSingleton().comment(MainActivity.this);
            }
        });

        //注销按钮
        findViewById(R.id.logout_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //设置为注销状态
                LoginContext.getSingleton().setState(new LogoutState());
            }
        });
    }
}

LoginActivity.class

public class LoginActivity extends AppCompatActivity {

    private static final String TAG = "LoginActivity";
    private EditText userNameEditText;
    private EditText pwdEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        userNameEditText = (EditText) findViewById(R.id.user_name_edit_text);
        pwdEditText = (EditText) findViewById(R.id.pwd_edit_text);
        findViewById(R.id.login_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                login();
                finish();
            }
        });
    }

    private void login() {
        String userName = userNameEditText.getText().toString().trim();
        String pwd = pwdEditText.getText().toString().trim();
        //执行网络请求,进行登陆...

        //登陆成功后修改为已登录状态
        LoginContext.getSingleton().setState(new LoginedState());
        Log.i(TAG, "登录成功");
    }
}

小结

优点

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

缺点

状态模式的使用必然会增加系统类和对象类的个数。

猜你喜欢

转载自blog.csdn.net/kavenka/article/details/82903987