一、了解状态模式
1、概念
状态模式又称状态对象模式,它允许一个对象在其内部状态改变的时候改变其行为。属于对象行为模式
2、结构
状态模式结构角色:
- 抽象状态角色:定义接口,用以封装环境角色对象的一个特定状态所对应的行为
- 具体状态角色:实现抽象状态角色所定义的接口
- 环境角色:定义客户端所需要的接口,并持有一个具体状态类的实例。这个具体状态类的实例代表此环境对象的现有状态。
3、简单示例
- 抽象状态类
package com.example.meitu.myapplication.state;
/**
* 状态模式—抽象状态角色
* @author: yongxiang.wei
* @version: 1.0.0, 2018/7/22 20:32
* @since: 1.0.0
*/
public interface State {
/**
* 抽象状态行为
*/
public void stateAction();
}
- 具体状态类
package com.example.meitu.myapplication.state;
/**
* 状态模式—具体状态角色
* @author: yongxiang.wei
* @version: 1.0.0, 2018/7/22 20:32
* @since: 1.0.0
*/
public class ConcreteState implements State{
/**
* 具体状态行为
*/
@Override
public void stateAction() {
System.out.println("concreteState action");
}
}
- 环境角色类
package com.example.meitu.myapplication.state;
/**
* 状态模式—环境角色
* @author: yongxiang.wei
* @version: 1.0.0, 2018/7/22 20:31
* @since: 1.0.0
*/
public class Context {
private State mCurrentState;
public State getCurrentState(){
return mCurrentState;
}
public void setCurrentState(State pState){
mCurrentState = pState;
}
public void currentStateAction(){
if(mCurrentState != null){
mCurrentState.stateAction();
}
}
}
- 客户端类
package com.example.meitu.myapplication.state;
/**
* @author: yongxiang.wei
* @version: 1.0.0, 2018/7/22 20:38
* @since: 1.0.0
*/
public class Client {
public static void main(String[] pArgs){
Context context = new Context();
State currentState = new ConcreteState();
context.setCurrentState(currentState);
context.currentStateAction();
}
}
4、优势与缺陷
- 优势:
(1)可以将将条件转移语句使用状态模式方式实现,如果条件转移语句过于过于庞大和复杂时,使用状态模式可以让程序免于大量的条件转移语句,便于维护
(2)避免使用一些属性来表称系统状态以及因状态表称属性使用不当带来的问题
(3)扩展性强,当有新的状态及其行为需要定义是时,可以很方便的通过设立子类的方式加到系统中,不需要改变其他类 - 缺陷:如果状态比较多,可能会造成大量的小状态类
5、使用场景
- 一个对象的行为依赖于它所处的状态,对象的行为必须随状态的改变而改变
- 对象在某个方法里依赖于一重或多重条件转移语句,其中有大量的代码。状态模式把条件转移语句的每个分支都包装到一个单独的类里,这使得条件转移分支能够以类的形式独立存在和演变。维护这些独立的类就不再影响到系统的其他部分。
二、问题及讨论
状态模式并没有规定在什么时间、谁来处理状态的转换、谁来定义状态的变化(下一状态是哪个状态)、状态对象的生命周期如何管理。
-
什么时间:
这个我觉得得要根据具体的业务来决定。比如登录模块,开始的时候是未登录状态,只有用户点击登录这个时间才会将状态变为登录中;接着只有在登录成功时才会将状态变为已登录状态 -
谁来处理状态的转换
我觉得是在环境角色里比较合适,因为环境角色持有当前的状态,在处理状态改变之前,先将环境角色中的当前状态退出,再将环境角色中的当前状态切换到新的状态 -
谁来定义状态的变化?
有三种角色可以决定状态的变化:
(1)环境角色
适合使用场景:转换条件固定
(2)具体状态角色
可以自行决定在合适使用哪个状态,灵活性较强
(3)外界事件 -
状态对象的生命周期(什么时候创建、什么时候销毁)
(1)动态创建状态对象,状态转换即销毁,减少不必要的对象:适合状态不频繁转换场景
(2)一开始就创建好所有状态对象,且在状态转换时不销毁:适合状态频繁转换,减少创建对象带来的开销,但是如果状态过多的话会造成内存上的开销
(3)创建好一种或几种频繁使用的状态对象:减少创建对象带来的开销
三、android中的应用
android源码中用到状态模式的比较少,framework中statemachine采用了状态模式,由于时间准备不充裕,还没时间看这块的的源码,这里先不展开介绍,待续。
四、项目中的应用
App开发基本上都会涉及到页面的开发,而页面大都会有以下几种状态:
-
加载中
加载中 - 加载成功显示页面内容
-
页面为空
页面为空
页面为空2 -
加载失败。
加载失败
如何管理这些不同的页面状态呢?刚开始的时候我这边使用的是构建模式的方式构建一个EmptyView来管理这些不同的状态和行为,但是在我想做分享的时候,感觉使用构建模式的方式不合理,会造成这个EmptyView愈来愈臃肿;综合考虑我觉得使用状态模式进行重构,下面重构页面状态的结构:
页面状态结构 - IPageState页面状态抽象角色
public interface IPageState {
/**
* 进入当前状态
* @param pStateContext 环境角色
*/
void enter(IPageStateContext pStateContext);
/**
* 退出当前状态
* @param pStateContext 环境角色
*/
void exit(IPageStateContext pStateContext);
}
- BasePageState页面状态基类
public abstract class BasePageState implements IPageState {
/**
* 当前状态所展示的View
*/
View mPageStateView;
/**
* 进入当前状态
* @param pStateContext 环境角色
*/
@Override
public void enter(IPageStateContext pStateContext) {
ViewGroup pParentView = pStateContext == null ? null : pStateContext.getPageStateParentView();
if(pParentView == null){
return;
}
if(pParentView instanceof LinearLayout){
pParentView.addView(getPageStateView(pParentView.getContext()), 0);
}else{
int childCount = pParentView.getChildCount();
pParentView.addView(getPageStateView(pParentView.getContext()), childCount);
}
}
/**
* 获取当前状态所展示的View
* @param pContext
* @return
*/
private View getPageStateView(Context pContext) {
if(pContext == null){
return null;
}
mPageStateView = LayoutInflater.from(pContext).inflate(pageStateResLayoutId(), null);
return mPageStateView;
}
@Override
public void exit(IPageStateContext pStateContext) {
ViewGroup pParentView = pStateContext == null ? null : pStateContext.getPageStateParentView();
if(pParentView == null || mPageStateView == null){
return;
}
pParentView.removeView(mPageStateView);
}
/**
* 当前页面状态所展示的View 布局资源id
* @return
*/
protected abstract int pageStateResLayoutId();
}
这里定义基类的原因是:因为其他的状态进入和退出状态基本都是将当前状态的view加入页面显示或从页面中移除
- NormalPageState页面正常状态
public class NormalPageState implements IPageState {
/**
* 进入正常状态
* @param pStateContext 环境角色
*/
@Override
public void enter(IPageStateContext pStateContext) {
}
/**
* 退出正常状态
* @param pStateContext 环境角色
*/
@Override
public void exit(IPageStateContext pStateContext) {
}
}
这里正常的页面状态行为设置为空,1是因为可以不改变当前页面正常状态的逻辑;2是正常的状态行为放在Activity中处理(可讨论是否合理,这个类甚至可以不要)
- LoadingState页面加载中状态
public class LoadingState extends BasePageState {
/**
* 当前页面状态所展示的View 布局资源id
* @return
*/
@Override
protected int pageStateResLayoutId() {
return R.layout.include_common_loading_state;
}
@Override
public void enter(IPageStateContext pStateContext) {
super.enter(pStateContext);
if(pStateContext != null){
pStateContext.onPageLoading();
}
}
}
主要是为了调用环境角色加载页面数据
- EmptyState页面空白状态
public class EmptyState extends BasePageState {
/**
* 当前页面状态所展示的View 布局资源id
* @return 返回空页面布局资源id
*/
@Override
protected int pageStateResLayoutId() {
return R.layout.include_common_empty_state;
}
/**
* 进入当前状态
* @param pStateContext 环境角色
*/
@Override
public void enter(final IPageStateContext pStateContext) {
super.enter(pStateContext);
if(mPageStateView != null){
mPageStateView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if(pStateContext != null){
pStateContext.onPageEmptyClick();
}
}
});
}
}
}
可以定制化空白页面的点击事件,让具体的环境角色去实现对于的业务逻辑
- PageErrorState页面加载失败状态
public class PageErrorState extends BasePageState {
/**
* 当前页面状态所展示的View 布局资源id
* @return
*/
@Override
protected int pageStateResLayoutId() {
return R.layout.include_common_error_state;
}
/**
* 进入当前状态
* @param pStateContext 环境角色
*/
@Override
public void enter(final IPageStateContext pStateContext) {
super.enter(pStateContext);
if(mPageStateView != null){
Button retryBtn = mPageStateView.findViewById(R.id.include_common_error_retry_btn);
retryBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if(pStateContext != null){
pStateContext.onPageErrorRetry();
}
}
});
}
}
}
可以定制化页面加载失败的点击事件,让具体的环境角色去实现对于的业务逻辑
- IPageStateContext页面状态环境接口
public interface IPageStateContext {
/**
* 构建加载中状态
* @param pMsg
* @return
*/
IPageState buildLoadingState(String pMsg);
/**
* 构建页面空白状态
* @return
*/
IPageState buildEmptyState();
/**
* 构建正常状态
* @return
*/
IPageState buildNormalPageState();
/**
* 构建页面加载失败状态
* @param pErrorCode
* @param pErrorMsg
* @return
*/
IPageState buildPageErrorState(int pErrorCode, String pErrorMsg);
/**
* 转换到新状态
* @param pNewPageState
*/
void transformToNewState(IPageState pNewPageState);
/**
* 获取页面状态布局所在的父级控件
* @return
*/
ViewGroup getPageStateParentView();
/**
* 页面加载中
*/
void onPageLoading();
/**
* 页面未空时的点击事件
*/
void onPageEmptyClick();
/**
* 页面重新加载
*/
void onPageErrorRetry();
}
- BaseActivity——Activity 基类,环境角色基类
public abstract class BaseActivity extends AppCompatActivity implements IPageStateContext{
/**
* 当前页面状态
*/
IPageState mPageState;
/**
* 页面内容容器
*/
ViewGroup mPageContentViewLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(contentViewLayoutId());
intView(savedInstanceState);
initPageState();
}
/**
* 重写contentView结构,改成LinearLayout,配置状态栏样式、标题栏等
*
* @param layoutResID contentViewLayoutId
*/
@Override
public void setContentView(int layoutResID) {
//重写ContentView
resetContentViewStruct();
if(mPageContentViewLayout != null){
LayoutInflater.from(this).inflate(layoutResID, mPageContentViewLayout, true);
}
}
/**
* 重写contentView结构,改成LinearLayout,配置状态栏样式、标题栏等
*/
private void resetContentViewStruct() {
ViewGroup viewGroup = (ViewGroup) findViewById(android.R.id.content);
viewGroup.removeAllViews();
mPageContentViewLayout = new LinearLayout(this);
((LinearLayout)mPageContentViewLayout).setOrientation(LinearLayout.VERTICAL);
viewGroup.addView(mPageContentViewLayout);
}
protected abstract int contentViewLayoutId();
protected abstract void intView(@Nullable Bundle savedInstanceState);
protected void initPageState(){
transformToNewState(buildLoadingState(null));
}
@Override
public ViewGroup getPageStateParentView(){
return mPageContentViewLayout;
}
/**
* 页面加载中
*/
@Override
public void onPageLoading() {
}
/**
* 页面未空时的点击事件
*/
@Override
public void onPageEmptyClick(){
}
/**
* 页面重新加载
*/
@Override
public void onPageErrorRetry() {
transformToNewState(buildLoadingState(null));
}
/**
* 转换到新状态
* @param pNewPageState
*/
@Override
public void transformToNewState(IPageState pNewPageState) {
if(pNewPageState == null){
return;
}
if(mPageState != null){
mPageState.exit(this);
}
mPageState = pNewPageState;
mPageState.enter(this);
}
/**
* 构建加载中状态
* @param pMsg
* @return
*/
@Override
public IPageState buildLoadingState(String pMsg){
return new LoadingState();
}
/**
* 构建页面空白状态
* @return
*/
@Override
public IPageState buildEmptyState(){
return new EmptyState();
}
/**
* 构建正常状态
* @return
*/
@Override
public IPageState buildNormalPageState(){
return new NormalPageState();
}
/**
* 构建页面加载失败状态
* @param pErrorCode
* @param pErrorMsg
* @return
*/
@Override
public IPageState buildPageErrorState(int pErrorCode, String pErrorMsg){
return new PageErrorState();
}
}
这里将Activity当做环境角色,用一个基类来实现环境角色的接口,在基类中定义基本的实现和状态的转换功能,具体的环境角色可以实现并定义自己的每个状态的不同表现
- GameContentActivity 具体环境角色
public class GameContentActivity extends BaseActivity implements IGetGameContentPresView {
TextView mContentTv;
GetGameContentPresenter mPresenter;
@Override
protected int contentViewLayoutId() {
return R.layout.activity_game_content;
}
@Override
protected void intView(@Nullable Bundle savedInstanceState) {
mContentTv = findViewById(R.id.get_games_content_tv);
mPresenter = new GetGameContentPresenter(this, this);
}
@Override
public void onPageLoading() {
mPresenter.getGameContent();
}
@Override
public void onGetGameContentSuccess(String pContent) {
if(mContentTv != null){
mContentTv.setText(pContent);
}
}
@Override
public void onGetGameContentFailure(int pErrorCode, String pErrorMsg) {
}
}
- GetGameContentPresenter 外界环境
public class GetGameContentPresenter {
private WeakReference<IPageStateContext> mPageStateContextRef;
private WeakReference<IGetGameContentPresView> mPresViewRef;
public GetGameContentPresenter(
IPageStateContext pPageStateContext,
IGetGameContentPresView pPresView){
mPageStateContextRef = new WeakReference<>(pPageStateContext);
mPresViewRef = new WeakReference<>(pPresView);
}
private IPageStateContext getPageStateContext(){
return mPageStateContextRef == null ? null : mPageStateContextRef.get();
}
private IGetGameContentPresView getPresView(){
return mPresViewRef == null ? null : mPresViewRef.get();
}
public void getGameContent(){
final Callback callback = new Callback<String>(){
@Override
public void onSuccess(String s) {
IPageStateContext pageStateContext = getPageStateContext();
if(pageStateContext != null){
if(TextUtils.isEmpty(s)){
pageStateContext.transformToNewState(pageStateContext.buildEmptyState());
}else {
pageStateContext.transformToNewState(pageStateContext.buildNormalPageState());
}
}
IGetGameContentPresView presView = getPresView();
if(presView != null){
presView.onGetGameContentSuccess(s);
}
}
@Override
public void onFailure(int pErrorCode, String pErrorMsg) {
IPageStateContext pageStateContext = getPageStateContext();
if(pageStateContext != null){
pageStateContext.transformToNewState(pageStateContext.buildPageErrorState(pErrorCode, pErrorMsg));
}
IGetGameContentPresView presView = getPresView();
if(presView != null){
presView.onGetGameContentFailure(pErrorCode, pErrorMsg);
}
}
};
//以下是网络请求获取游戏内容,在此不展开
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
callback.onSuccess("true");
}
}, 2000);
}
public static abstract class Callback<T>{
public abstract void onSuccess(T t);
public abstract void onFailure(int pErrorCode, String pErrorMsg);
public void onComplete(){
}
}
}
让外界条件角色去决定什么时候转换状态以及转换成哪个状态
五、参考
- java与设计模式(书籍)
- java23种设计模式(书籍)
- java 23种设计模式 深入理解
网友评论