频道栏目
首页 > 资讯 > 其他 > 正文

Java设计模式之——状态模式

16-11-04        来源:[db:作者]  
收藏   我要投稿

状态方法模式简单介绍

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

状态模式的定义

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

状态模式的使用场景

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

状态模式将每一个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖其他对象而独立变化,这样通过多态来去除过多的、重复的 if-else 等分支语句。

状态模式的 UML 类图

这里写图片描述

角色介绍:

Context —— 环境类,定义客户感兴趣的接口,维护一个 State 子类的实例,这个实例定义了对象的当前状态。 State —— 抽象状态类或者状态接口,定义一个或者一组接口,表示该状态下的行为。 ConcreteStateA、ConcreteStateB —— 具体状态类,每一个具体的状态类实现抽象 State 中定义的接口,从而达到不同状态下的不同行为。

状态模式实战

在开发过程中,我们用状态模式最常见的地方应该是用户登录系统,在用户已登录和未登录的情况下,对于同一事件的处理行为是不一样的,例如,在新浪微博中,用户在未登录的情况下点击转发按钮,此时会先让用户登录,然后在执行转发操作;如果是已登录的情况下,那么用户输入转发的内容后就可以直接进行操作。可见,在这两种状态下,对于转发这个操作的处理动画完全是不一样的,当状态改变时对于转发操作的行为发生了改变。

下面我们用状态模式来简单实现这个过程,首先创建一个 Android 项目,里面含有两个 Activity,分别为 MainActivity、LoginActivity,MainActivity 是应用第一个 Activity,有转发和注销用户功能,LoginActivity 则为用户登录界面。

用户的默认状态为为登录状态,此时用户在 MainActivity 界面点击转发时回先跳转到登录界面,然后在登录界面登录成功后在回到 MainActivity 页面,此时,用户在进行转发操作就可以实现真正的转发功能,

MainActivity 的代码如下:

    @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 v) {
                LoginContext.getInstance().forward(MainActivity.this);
            }
        });

        findViewById(R.id.logout_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginContext.getInstance().setState(new LogoutState());
            }
        });
}

LoginActivity 中我们并没有太复杂的逻辑,这里只提供了一个登录按钮,点击登录按钮就表示已成功登录,成功登录之后将 LoginContext 的状态设置为已登录状态,并且返回到 MainActivity 页面,具体代码如下:

public class Loginactivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.activity_login);
        findViewById(R.id.login_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
                finish();
            }
        });
    }

    private void login() {
        //1、验证登录用户名和密码
        //2、执行网络请求,进行登录...
        //3、登录成功后修改为登录状态
        LoginContext.getInstance().setState(new LoginedState());
        Toast.makeText(this, "登录成功", Toast.LENGTH_LONG).show();
    }
}

这里的 LoginContext 就是状态模式中的 Context 角色,是用户的操作对象和状态管理对象, LoginContext 将相关操作委托给状态对象。这样在状态发生改变时,LoginContext 的行为就发生了改变,我们看看 LoginContext 的相关代码:

public class LoginContext {
    UserState mState = new LogoutState();


    private LoginContext() {
    }

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

    public static LoginContext getInstance() {
        return LoginContextHolder.sInstance;
    }

    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 来对状态进行修改,并且将操作委托给状态对象。不同的状态对象对于同一个操作进行不同的处理,状态对象相关代码如下所示:

/**
 * 用户状态
 */
public interface UserState {
    /**
     * 转发
     */
    void forward(Context context);

    /**
     * 评论
     */
    void comment(Context context);

    //可能还会存在 查看用户收获地址、用户个人信息、用户账户余额等的操作....
}

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

    @Override
    public void comment(Context context) {
        Toast.makeText(context, "评论微博", Toast.LENGTH_LONG).show();
    }
}

/**
 * 未登录状态
 */
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);
    }
}

在 UserState 中有两个方法,即转发和评论。在已登录的状态下,用户调用这两个函数都会执行相应的操作(或跳到指定的界面);但是在未登录的情况下则会跳转到登录界面,当用户登录成功后,才能进行这些操作。也就是说,在用户状态改变之后用户的行为也发生了改变。试想一下,如果没有使用状态模式,那么在任何调用转发或评论功能的地方都需要进行是否登录的判断,或者在添加一个层次对这个逻辑判断进行统一的封装,如果已登录则执行相应的功能,否则跳转到登录界面。代码逻辑大致如下:

 private LoginContext() {
        /**
         * 一般应用中记录用户是否已经登录基本上都是在本地进行保存一个状态来区分用户是否登录,这里我们可以在初始时就
         * 获取一下本地保存的状态,以便后面所有的操作都可以顺利进行
         */
        if (true) {     //表示本地存在已登录状态,当执行相应的操作时,可以直接跳转到指定界面
            mState = new LoginedState();
        } else {      //表示本地没有用户的登录状态,当执行相应的操作时,需要跳转到登录界面进行登录
            mState = new LogoutState();
        }
    }

但不管哪种形式,都会存在 if-else 的重复逻辑。如果再增加一个用户状态,那么每一个功能函数都需要添加对这个状态的判断。如果增加一个功能,也要在这个函数中对所有的状态进行判断,然后根据状态进行相应的功能。这使得这个模块很脆弱,一旦忘了某个用户状态进行判断,那么很可能引发用户权限问题。状态模式将这些行为封装到状态类中,不仅消除了重复的 if-else 逻辑、结构更为清晰,也使得这个模块可扩展性、灵活性更高。

其实通过上面一个例子,我们完全可以发现状态模式的好处:上面的那个例子通过 LoginContext 来管理用户的登录状态(UserState)已经要执行的操作,而且通过 用户的登录状态(UserState)来根据当前用户的登录状态来执行相应的操作(已登录和未登录虽然调用的是同一个函数,但是执行的效果却不同),来实现用户登录状态的管理,这就降低了以后代码维护的成本以及风险。再举个例子吧:比如现在你的 App 中有这些功能:用户账户管理、用户个人信息、等等相关的需要用户登录才能进行操作的界面,那么如果你没有采用状态模式进行用户登录状态管理,那么你是不是需要在每个需要用户登录之后才能查看的界面的入口出都要进行判断(判断用户是否登录,已登录进入到相应界面,未登录跳转登录界面进行登录),那么问题可能就出来了,如果说项目的后期升级后,在用户为登录界面下我不想跳入登录界面或者其他原因,反正就是本来要跳入登录界面的逻辑要改了,那么你怎么办?是不是需要在原本跳入登录界面的地方都需要手动进行更改?是不是很繁琐?会不会引发其他问题呢?这就是个头疼的问题;如果采用了状态模式,那么如果突然出现上面的情况,你还需要头疼么?因为咱们的未登录状态是单独管理的,我们只需要对这个未登录的状态类进行更改就完事了,是不是感觉简单了很多。这个需要大家自己体会….

总结

状态模式的关键点在于不同的状态下对于同一行为有不同的响应,这其实就是一个将 if-else 用多态来体现的一个具体示例。在 if-else 或者 switch-case 形势下根据不同的状态进行判断,如果是状态 A那么就执行 A 方法,状态是B那就执行B方法,但这种实现使得逻辑耦合在一起,易于出错,通过状态模式能够很好地消除这类“丑陋”的逻辑处理,当然并不是任何出现 if-else 的地方都应该通过状态模式重构,模式的运用一定要考虑所处的情景以及你要解决的问题,只有符合特定的场景才建议使用对应的模式。

状态模式的优点:

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

状态模式的缺点:

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

相关TAG标签
上一篇:iOS 多线程编程(十五、RunLoop简单介绍)
下一篇:配置超过64K方法的应用程序
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站