Android设计模式-状态模式

状态模式介绍

状态模式中的行为由状态决定,不同的状态下有不同的行为。状态模式和策略模式的结构几乎完全一样,但它们的目的和本质完全不同。状态模式是平行的、不可替换的。用一句话表述,策略模式是彼此独立、可替换的。一句话描述就是,状态模式把对象的行为包装在不同状态的对象中,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象再其内部状态改变的时候,其行为也随之改变。

状态模式定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来是改变了其类。

使用场景

(1)一个对象的行为取决于它的状态,并且必须在运行时根据状态改变它的行为。

(2)代码中包含大量的与对象状态有关的条件语句,如操作中更包含大量的if-else或者switch-case且这些分支依赖于该对象的状态。

状态模式的简单示例

下面以电视遥控器来演示下状态模式的实现,电视分为开机和关机状态,开机状态下可以通过遥控器尽心频道切换、调整音量等操作。但此时重复开机是无效的;而在关机状态下,频道切换、调整音量、关机都是无效的,只有开机按钮有效,也就是说,电视的内部状态决定了遥控器的行为,先看下第一版的实现:

//电视遥控器,含有开机、关机、下一频道、上一频道、调高音量、调低音量这几个功能
public class TvController {
    //开机状态
    private final static int POWER_ON = 1;
    //关机状态
    private final static int POWER_OFF = 2;
    private int mState = POWER_OFF;
    public void powerOn() {
        mState = POWER_ON;
        if(mState == POWER_OFF) {
            System.out.println("开机啦!");
        }
    }
    public void powerOff() {
        mState = POWER_OFF;
        if(mState == POWER_ON) {
            System.out.println("关机啦!");

        }
    }
    public void nextChannel() {
        if(mState == POWER_ON) {
            System.out.println("下一频道!");
        } else {
            System.out.println("没有开机!");
        }
    }
    public void prevChannel() {
        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("没有开机!");
        }
    }

}

在TVController类中,通过mState字段来判断电视的开机与关机状态。并通过判断这个字段来决定不同的操作是否该执行。如果状态不是两个而是变得更多、遥控器的功能变得更多,这就需要更多的if-else条件判断,而这些代码都充斥在一个类中。这就使得这个类变得更加难以维护。

状态模式就是为了解决该问题而出现的。我们将这些状态用对象代替,将这些状态封到对象中,使得在不同的对象中有不同的实现,这样就可以将这些if-else从TVController类中去掉:

//电视状态接口,定义了电视操作的函数
public interface TvState {
    public void nextChannel();
    public void prevChannel();
    public void turnUp();
    public void turnDown();
}


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

    }
    @Override
    public void prevChannel() {

    }
    @Override
    public void turnUp() {

    }
    @Override
    public void turnDown() {

    }


}



//开机状态,此时再触发开机功能不做任何操作
public class PowerOnState implements TvState {
    @Override
    public void nextChannel() {
        System.out.println("下一频道");

    }
    @Override
    public void prevChannel() {
        System.out,println("上一频道");
    }
    @Override
    public void turnUp() {
        System.out,println("调高音量");

    }
    @Override
    public void turnDown() {
        System.out,println("调低音量");
    }


}


//电源操作接口
public interface PowerController {
    void powerOn();
    void powerOff();
}


//电视遥控器
public class TvController implements PowerController {
    TvState mState;
    public void setTvState(TvState mTvState) {
        this.mState = mTvState;
    }
    @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 prevChannel() {
        mTvState.prevChannel();
    }

    public void turnUp() {
        mTvState.turnUp();
    }
    public void turnDown() {
        mTvState.turnDown();
    }
}

下面是客户端代码:

public class Client {
    public static void main(String[] args) {
        TvController tvController = new TvController();
        //设置开机状态
        tvController.powerOn();
        //下一频道
        tvController.nextChannel();
        //调高音量
        tvController.turnUp();
        //设置关机状态
        tvController.powerOff();
        //调高音量,此时不会生效
        tvController.turnUp();
    }
}
//输出

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

在现实中,抽象了一个TvState接口,该接口中有操作电视的所有函数,并且有两个实现类:开机状态和关机状态。不同状态下的同一个操作会有不同的相应。

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

状态模式实战

在Android开发中,状态模式最常见的地方应该是用户登录系统。在用户已登录和未登录的情况下,对于同一点击事件的响应行为是不一样的。比如,在新浪微博中,用户再未登录的情况下点击转发按钮,此时会先让用户登录,然后在执行转发。而如果是已登录,则直接进行转发操作即可。

下面就演示了这个登陆过程:有两个Activity,一个是MainActivity,它是进如应用的第一个界面,上面有两个按钮,一个用于转发,一个用于注销;另一个Activity是LoginActivity ,它是用户登录界面。

用户默认状态时未登录,此时用户再MainActivity中点击转发按钮就会先条转到LoginActivity,待登陆完毕后再回到MainActivity。此时用户再点击转发按钮就能实现真正的转发功能。

//MainActivity
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        //转发按钮
        findViewById(R.id.forward_btn).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginContext.getLoginContext().forward(MainActivity.this);

            }
        });
        //注销按钮
        findViewById(R.id.logout_btn).setOnClickListener(new OnClickListener() {
            //设置为注销状态
            LoginContext.getLoginContext().setState(new LogOutState());
        });
    }
} 

LoginActivity则是在用户输入用户名和密码后登陆,成功后将LoginContext的状态设为已登录状态,并返回MainActivity页面:

//LoginActivity
public class LoginActivity extends activity {
    EditText userNameEditText;
    EditText pwdEditText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        //绑定控件
        ... ...
        //登录按钮
        ... ...
        @Override
        public void onClick(View v) {
            login();
            finish();
        }
        private void login() {
            String userName = userNameEditText.getText().toString().trim();
            String pwd = pwdEditText.getText().toString().trim();
            //执行网络请求,进行登录......

            //登陆后修改为已登录状态
            LoginContext.getLoginContext().setState(new LoginedState());
            Toast.makeText(getApplicationContext(),"登录成功", Toast.LENGTH_LONG).show();
        }

    }
}

这里的LoginContext就是“遥控器”这个角色,是用户的操作对象和状态管理对象。LoginContext将相关操作委托给状态对象,这样在状态改变时,LoginContext的行为就发生了改变:

//LoginContext
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) {
        this.mState = state;
    }
    //转发
    public void forward(Context context) {
        mState.forward(context);
    }
    public void comment(Context context) {
        mState.comment(context);
    }
}

LoginContext通过setState来对状态进行修改,并且把操作委托给mState成员。不同的状态对象对于同一个操作进行不同的处理:

//用户状态接口
public interface UserState {
    //转发
    public void forward(Context context);
    //评论
    public void conmment(Context context);
}

//已登录状态
public class LoginedState implements UserState {
    @Override
    public void forward(Context context) {
        Toast.makeText(getApplicationContext(),"转发微博", Toast.LENGTH_LONG).show();
    }
    @Override
    public void comment(Context context) {
        Toast.makeText(getApplicationContext(),"评论微博", Toast.LENGTH_LONG).show();
    }
}


//注销状态
public class LoginOutState implements UserState {
    @Override
    public void forward(Context context) {
        gotoLoginAcitvity(context);
    }
    @Override
    public void comment(Context context) {
        gotoLoginAcitvity(context);
    }
    protected void gotoLoginAcitvity(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }
}

在UserState中有两个方法,转发和评论。已登录状态和未登录状态下,它们的操作并不相同。

总结

状态模式的关键点在于不同的状态下对于同一种香味的不同响应,这其实就是一个将if-else用多态来实现的一个具体示例。但是这比if-else模式更加简洁、扩展性更好、更加美观。但这并不意味着任何使用if-else的地方都必须使用状态模式,这要根据特定的场景来决定。

优点:

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

缺点:

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

    原文作者:vanpersie_9987
    原文地址: https://blog.csdn.net/vanpersie_9987/article/details/70853376
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞