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

一、介绍

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

二、定义

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

三、使用场景

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

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

四、状态模式的UML类图

UML类图:

这里写图片描述

角色介绍:

  • Context:环境类,定义客户感兴趣的接口,维护一个State子类的实例,这个实例定义了对象的当前状态。

  • State:抽象状态类或状态接口,定义一个或者一组接口,表示该状态下的行为。

  • ConcreteStateA、ConcreteStateB:具体状态类,每一个具体的状态类实现抽象State中定义的接口,从而达到不同状态下的不同行为。

五、简单示例

下面我们就以电视遥控器为例来演示一下状态模式的实现。我们首先将电视的状态简单分为开机状态和关机状态,在开机状态下可以通过遥控器进行频道切换、调整音量等操作,但是,此时重复按开机键是无效的;而在关机状态下,频道切换、调整音量、关机都是无效的操作,只有按开机按钮时才会生效。也就是说电视的内部状态决定了遥控器的行为。

首先是普通的实现方法:

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(){if(mState ==POWER_OFF){System.out.println("电视开机了");}mState = POWER_ON;}public void powerOff(){if(mState ==POWER_ON){System.out.println("电视关机了");}mState = POWER_OFF;}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("没有开机");}}
}

可以看到,每次执行通过判断当前状态来进行操作,部分的代码重复,假设状态和功能增加,就会越来越难以维护。这时可以使用状态模式,如下:

电视的状态接口:

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

关机状态:

/*** * 关机状态,操作无结果* * */
public class PowerOffState implements TVState{@Overridepublic void nextChannel() {}@Overridepublic void prevChannel() {}@Overridepublic void turnUp() {}@Overridepublic void turnDown() {}
}

开机状态:

/*** * 开机状态,操作有效* * */
public class PowerOnState implements TVState{@Overridepublic void nextChannel() {System.out.println("下一频道");}@Overridepublic void prevChannel() {System.out.println("上一频道");}@Overridepublic void turnUp() {System.out.println("调高音量");}@Overridepublic void turnDown() {System.out.println("调低音量");}
}

电源操作接口:

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

电视遥控器:

/*** 电视遥控器* * */
public class TVController implements PowerController{TVState mTVState;public void setTVState(TVState mTVState){this.mTVState = mTVState;}@Overridepublic void powerOn() {setTVState(new PowerOnState());System.out.println("开机了");}@Overridepublic 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.turnDown();}
}

输出结果如下:

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

上述实现中,我们抽象了一个TVState接口,该接口中有操作电视的所有函数,该接口有两个实现类,即开机状态(PowerOnState)和关机状态(PowerOffState)。开机状态下只有开机功能是无效的,也就是说在已经开机的时候用户在按开机键不会产生任何反应;而在关机状态下,只有开机功能是可用的,其他功能都不会生效。同一个操作,如调高音量的turnUp函数,在关机状态下无效,在开机状态下就会将电视的音量调高,也就是说电视内部状态影响了电视遥控器的行为。状态模式将这些行为封装到状态类中,在进行操作时将这些功能转发给状态对象,不同的状态有不同的实现,这样就通过多态的形式去除了重复、杂乱的if-else语句,这也正是状态模式的精髓所在。

六、Android实战中的使用

1、登录系统,根据用户是否登录,判断事件的处理方式。
2、Wi-Fi管理,在不同的状态下,WiFi的扫描请求处理不一。

下面以登录系统为例讲解下状态模式在实战中的使用:

在android开发中,我们遇到登录界面是十分常见的,而状态设计模式在登录界面的应用十分广泛,用户在登录状态下和未登录状态下,对逻辑的操作是不一样的。例如最常见的情况就是在玩新浪微博的时候,用户在登录的情况下才能完成评论和转发微博的操作;而当用户处于未登录的情况下要执行转发和评论微博的操作需要进入登录界面登录以后才能执行,所以面对这两者不同的状况,利用状态设计模式来设计这个例子最好不过。

1、状态基类
前面我们讲过状态设计模式的原理实则是多态,在这里我们用UserState接口表示此基类,包换转发操作和评论这两种状态,代码如下:

public interface UserState {/*** 转发操作* @param context*/public void forword(Context context);/*** 评论操作* @param context*/public void commit(Context context);
}

2、用户在登录和未登录两种状况下的实现类LoginState和LogoutState;代码如下:

在LoginState.java中,用户是可以执行转发和评论操作。

public class LoginState implements UserState{@Overridepublic void forword(Context context) {Toast.makeText(context, "转发成功", Toast.LENGTH_SHORT).show();}@Overridepublic void commit(Context context) {Toast.makeText(context, "评论成功", Toast.LENGTH_SHORT).show();}   
}

在LogoutState.java中,用户在未登录的情况下不允许执行操作,而是应该跳转到登录界面执行登录以后才可以执行。

public class LogoutState implements UserState{/*** 跳转到登录界面登录以后才能转发*/@Overridepublic void forword(Context context) {gotoLohinActivity(context);}/*** 跳转到登录界面登录以后才能评论*/@Overridepublic void commit(Context context) {gotoLohinActivity(context);}/*** 界面跳转操作* @param context*/private void gotoLohinActivity(Context context){context.startActivity(new Intent(context,LoginActivity.class));}
}

3、操作角色LoginContext
这里的LoginContext就是在状态模式的Context角色,是用户操作对象和管理对象,LoginContext委托相关的操作给状态对象,在其中状态的发生改变,LoginContext的行为也发生改变。LoginContext的代码如*下:
温馨提示:
这里我们用到单例就是为了全局只有一个LoginContext去控制用户状态;

public class LoginContext {//用户状态默认为未登录状态UserState state = new LogoutState();private LoginContext(){};//私有构造函数,避免外界可以通过new 获取对象//单例模式public static LoginContext getInstance(){return SingletonHolder.instance;}/***静态代码块*/private static class SingletonHolder{private static final LoginContext instance = new LoginContext();}public void setState(UserState state){this.state = state;}//转发public void forward(Context context){state.forword(context);}//评论public void commit(Context context){state.commit(context);}
}

4、界面展示
LoginActivity.java,此界面执行登录操作,登录成后把 LoginContext.getInstance().setState(new LoginState());设置为登录状态,在MainActivity中就执行的是登录状态下的操作,即可以转发可评论;

public class LoginActivity extends Activity implements OnClickListener{private static final String LOGIN_URL = "http://10.10.200.193:8080/Day01/servlet/LoginServlet";private EditText et_username;private EditText et_password;private Button btn_login;private String username;private String password;private KJHttp http;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);initView();initData();}private void initView() {et_username = (EditText) findViewById(R.id.et_username);et_password = (EditText) findViewById(R.id.et_password);btn_login = (Button) findViewById(R.id.btn_login);btn_login.setOnClickListener(LoginActivity.this);}private void initData() {http = new KJHttp();}/*** 执行登录操作* * @param username2* @param password2*/protected void sendLogin(String username2, String password2) {HttpParams params = new HttpParams();params.put("username", "user1");params.put("password", "123456");http.post(LOGIN_URL, params, new HttpCallBack() {@Overridepublic void onSuccess(String t) {if ("200".equals(t)) {//设置为登录状态LoginContext.getInstance().setState(new LoginState());startActivity(new Intent(LoginActivity.this,MainActivity.class));finish();Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();}}});}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_login:username = et_username.getEditableText().toString().trim();password = et_password.getEditableText().toString().trim();if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {Toast.makeText(LoginActivity.this, "用户名密码不能为空", Toast.LENGTH_SHORT).show();return;}sendLogin(username, password);break;}}}

MainActivity.java,在用户登录成功后,点击转发和评论执行的是登录状态下的操作,而当用户注销时,我们把LoginContext的状态设置为未登录状态;LoginContext.getInstance().setState(new LogoutState());此时在点击转发和评论操作时就会跳到用户登录界面。

public class MainActivity extends Activity {private Button btn_forward;private Button btn_commit;private Button btn_logout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();initListener();}private void initView() {btn_forward = (Button) findViewById(R.id.btn_forward);btn_commit = (Button) findViewById(R.id.btn_commit);btn_logout = (Button) findViewById(R.id.btn_logout);}private void initListener() {//转发操作btn_forward.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//调用LoginContext里面的转发函数LoginContext.getInstance().forward(MainActivity.this);}});//评论操作btn_commit.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//调用LoginContext里面的转发函数LoginContext.getInstance().commit(MainActivity.this);}});//注销操作btn_logout.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//设置为注销状态LoginContext.getInstance().setState(new LogoutState());}});}
}

七、总结

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

优点:

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

缺点:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/447018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android设计模式之——责任链模式

一、介绍 责任链模式(Iterator Pattern),是行为型设计模式之一。什么是”链“?我们将多个节点首尾相连所构成的模型称为链,比如生活中常见的锁链,就是由一个个圆角长方形的铁环串起来的结构。对于链式结构…

目前基于区块链的档案防篡改系统的设计如何实现防篡改

架构设计图 分析 为了保障档案数据的安全性和隐私性,存储档案附件和档案属性存储加密存储在私有IPFS集群,档案的IPFS地址和数字指纹存储在私有区块链上。公有区块链定期存储和检查私有区块链最新不可逆区块的高度和哈希值,以保障私有区块链上…

IPFS的文件存储模式

IPFS是如何进行文件存储的 IPFS采用的索引结构是DHT(分布式哈希表),数据结构是MerkleDAG(Merkle有向无环图) DHT(分布式哈希表) 参考链接MerkleDAG(Merkle有向无环图) 参考链接MerkleDAG功能…

Android设计模式之——解释器模式

一、介绍 解释器模式(Interpreter Pattern)是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式,该模式定义了一个表达式接口,通过该接口解释一个特定的上下文。在这么多的设计模式中&#xff0c…

在Docker里面安装Ubuntu,并且使用ssh进行连接

创建Ubuntu镜像 1,拉取Ubuntu系统的镜像 docker pull ubuntu2、查看拉取是否成功 docker images3,运行容器 docker run --name 新建的容器的名字 -ti -v /AAA:/BBB -d -p 3316:22 ubuntu(这个是镜像的名字)宿主机根目录中的AAA文件夹就映射到了容器…

Android设计模式之——命令模式

一、介绍 命令模式(Command Pattern),是行为型设计模式之一。命令模式相对于其他的设计模式来说并没有那么多的条条框框,其实它不是一个很”规范“的模式,不过,就是基于这一点,命令模式相对于其…

Android设计模式之——观察者模式

一、介绍 观察者模式是一个使用率非常高的模式,它最常用的地方是GUI系统、订阅——发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。以GUI系统来说&#xff0…

Android设计模式之——备忘录模式

一、介绍 备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态,这有点像我们平时说的”后悔药“。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存…

C++ using的三种使用策略以及具体的用法

Using的使用方法 1,命名空间的使用 为了防止代码冲突,都会使用到命名空间。假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分他们,我们在使用名字之外,不得不使用一些额外的信息&#…

Android设计模式之——迭代器模式

一、介绍 迭代器模式(Iterator Pattern)又称为游标(Cursor)模式,是行为型设计模式之一。迭代器模式算是一个比较古老的设计模式,其源于对容器的访问,比如Java中的List、Map、数组等&#xff0c…

Android设计模式之——模板方法模式

一、介绍 在面向对象开发过程中,通常会遇到这样的一个问题,我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的,或者说某些步骤的实现是会随着环境的变化而改变…

Android设计模式之——访问者模式

一、介绍 访问者模式是一种将数据操作与数据结构分离的设计模式,它是《设计模式》中23种设计模式中最复杂的一个,但它的使用频率并不高,正如《设计模式》的作者GOF对访问者模式的描述:大多数情况下,你不需要使用访问者…

Android设计模式之——中介者模式

一、介绍 中介者模式(Mediator Pattern)也称为调解者模式或调停者模式,Mediator本身就有调停者和调解者的意思。 在日常生活中调停者或调解者这个角色我们见得比较多的是“和事老”,也就是说调解两个有争端的人的角色&#xff0…

Java基础——虚拟机结构

一、Java平台结构图二、JVM、JRE和JDK关系JVM:Java Virtual Machine(Java虚拟机),负责执行符合规范的Class文件 JRE: Java Runtime Environment (java运行环境),包含JVM和类库 JDK&a…

C++:MAC安装Boost库文件并且使用CLion开发

boost的filestem库 C在17版本的标准库中引入了一个filesystem库,用来处理文件路径,以及文件访问。很多编译器对filesystem库的支持还不是很好。为了解决这个问题,可以临时使用boost::filesystem来替代。其实C17标准中的filesystem库就是从bo…

Java基础——Java异常处理机制

一、引言 try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解。不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话。不信?那你看看下面的代码…

clion在使用sqlite3的时候,显示Undefined symbols for architecture x86_64错误的解决办法

显示Undefined symbols for architecture x86_64错误的原因 1、缺少静态库 环境:在模拟器上报错但在真机上能运行成功,而且报的错误来自于第三方库。原因:architecture x86_64 是指模拟器的架构,意思就是 Crypto 变量在模拟器架…

Java基础——类加载机制及原理

一、什么是类的加载? 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Cl…

在Ubuntu环境下使用vcpkg安装sqlite_orm包文件

Ubuntu安装vcpkg 从github下载vcpkg的安装包,在usr/local路径下面执行如下命令 git clone https://github.com/Microsoft/vcpkg.git cd vcpkg //进入源码目录 ./bootstrap-vcpkg.sh //执行./bootstrap-vcpkg.sh进行编译安装,这个过程很慢 编译安装好…

window电脑查看ssh公钥,以及将自己的公钥添加到Github等类似网站

查看本机的ssh公钥 使用命令 cd ~/.ssh使用命令 ls 可以看到 id_rsa id_rsa.pub known_hosts 三个文件,此处需要的是id_rsa.pub文件使用命令 cat id_rsa.pub 查看文件的内容拷贝这段内容 添加自己的公钥 进入账户的设置页面参照如下步骤,进入SSH Key…