[即学即用] Android开发——状态模式

6,160 阅读6分钟

前言

相信 “大多数” 伙伴看了相关设计模式书籍、文章,一看就会,一写就 ***
在实际开发中也很少用上,或者说不知道在哪用,缺少应用场景,久而久之就忘记了~

在这篇文章中,我分享一下,我在Android 开发中使用到到设计模式——状态模式,希望大家喜欢~

需求

假设我们有这样一个需求 (文章类 App)

当用户点击头像的时候

  • 未登录状态:点击头像,则跳转到 登录页面在进行登录
  • 登录状态:点击头像,则不采取任何操作

编码

接到需求后,这不是so easy 吗?一顿霹雳吧啦敲代码

class MainActivity :AppCompatActivity(){

    private val isLogin: Boolean by Preference("isLogin", false)

    ...省略部分代码

    override fun initView() {

        // 点击头像
        mIvPortrait.setOnClickListener {
            if (isLogin) {
                startActivity<LoginActivity>()
            }
        }
    }

    ...省略部分代码
}

Preference 封装了 SharedPreferences ,Kotlin 的委托语法,不是本文重点,这里就不过多介绍了,如有不了解的小伙伴,可以私下百度学习~

通过 SharedPreferences 保存一个 用户是否登录(isLogin) boolean 变量,然后根据该变量,来判断是否进行登录页面跳转。

逻辑相对简单,暂时看起来没啥问题, 接下来我们在进一步看看扩展需求

扩展需求

当用户点击文章收藏的时候

  • 未登录状态:点击收藏,则跳转到 登录页面在进行登录
  • 登录状态:点击收藏,则发起收藏请求,文章进行收藏

编码

class MainActivity :AppCompatActivity(){

    private val isLogin: Boolean by Preference("isLogin", false)

    ...省略部分代码

    override fun initView() {

        // 点击头像
        mIvPortrait.setOnClickListener {
            if (isLogin) {
                startActivity<LoginActivity>()
            }
        }
        
        // 点击收藏按钮
        mBtnCollect.setOnClickListener {
            if (isLogin) {
                viewModel.collect(articleId)
            } else {
                startActivity<LoginActivity>()
            }
        }
    }

    ...省略部分代码
}

同理,我们在 点击收藏按钮处理逻辑上,也是根据 isLogin变量,来进行判断是否收藏,再进行相应逻辑处理。这里我们可以嗅得到代码的坏味道,重复的 登录状态 判断。

若我们在增加一个 当用户点击分享按钮进行文章分享,我们再次添加判断逻辑,如下代码:

// 点击收藏按钮
mBtnShare.setOnClickListener {
    if (isLogin) {
        viewModel.share(articleId)
    } else {
        startActivity<LoginActivity>()
    }
}

这样在我们 Android 项目中,会充满大量 登录逻辑判断,这样代码的质量大打折扣,不利于后续开发维护

思考

如果是你,你会怎么做?

当然是 CV 代码,又不是不能用 :)

若CV 代码,那么你肯定会给同事/大佬口吐芬芳: ***

废话不多说,进入正文,设计模式——状态模式

状态模式

介绍:状态模式中的行为是由状态来决定的,不同的状态下有不同的行为。
定义:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变来其类。

就如上文所说,当用户登录状态下,会有收藏点赞分享等操作行为。
若用户没有登录,则需要跳转到 登录页面进行 登录,才可以进行后续操作。

而这个用户状态的改变(未登录-> 登录),是在一个对象内部的,外在是无感知的。但实际行为应 状态的改变而改变。

说起来可能有点绕哈。我们来看看实际代码演示

UserState

interface UserState {

    fun collect(context: Context?, block: () -> Unit)
    
    fun share(context: Context?, block: () -> Unit)

    fun login(context: Context?)
}

我们先创建一个 UserState 接口类,结合上面背景,这里定义 logincollectshare 方法 (行为),然后我们分别创建 登录状态(LoginState) 类,未登录状态(LogoutState)类,并实现 UserState 接口

LoginState

class LoginState : UserState {

    override fun collect(context: Context?, block: () -> Unit) {
        // 发起收藏
        block()
    }
    
    override fun share(context: Context?, block: () -> Unit) {
        // 发起分享
        block()
    }

    // 已登录状态 无须登录 不做任何操作
    override fun login(context: Context?) {}
}

在登录状态下,我们执行相应的逻辑操作,登录状态下,无须登录,所以这里不做任何操作

LogoutState

class LogoutState : UserState {

    // 收藏
    override fun collect(context: Context?, block: () -> Unit) {
        goLoginActivity(context)
    }
    
    // 分享
    override fun share(context: Context?, block: () -> Unit) {
        goLoginActivity(context)
    }

    // 登录
    override fun login(context: Context?) {
        goLoginActivity(context)
    }
    
    // 跳转到登录
    private fun goLoginActivity(context: Context?) {
        context?.run {
            toast(getString(R.string.please_login))
            startActivity<LoginActivity>()
        }
    }
}

这里相信大家也已经猜到,未登录状态下,统一跳转到 登录页面进行登录,在进行后续操作

然后我们在定一个 管理状态的类 UserContext,方便管理我们的用户状态

UserContext

object UserContext{

    // 持久化存储 登录状态
    private var isLogin: Boolean by Preference(Key.LOGIN, false)

    // 设置默认状态
    var mState: UserState = if (isLogin) LoginState() else LogoutState()

    // 收藏
    fun collect(context: Context?, block: () -> Unit) {
        mState.collect(context, block)
    }
    
     // 分享
    fun share(context: Context?, block: () -> Unit) {
        mState.share(context, block)
    }

    // 登录
    fun login(context: Activity?) {
        mState.login(context)
    }
    
    // 切换成 登录状态
    fun setLoginState(){
        // 改变 sharedPreferences   isLogin值
        isLogin = true
        mState = LoginState()
    }
    
    // 切换成 未登录状态
    fun setLogoutState(){
        // 改变 sharedPreferences   isLogin值
        isLogin = false
        mState = LogoutState()
    }
}

UserContext管理着 用户的状态,mState 变量默认初始化为 未登录状态,并声明切换登录状态未登录状态切换的方法,方便后续切换状态使用,最后回到 MainActivity中,我们来看看 实际如何使用。

MainActivity

class MainActivity :AppCompatActivity(){

    //private val isLogin: Boolean by Preference("isLogin", false)

    ...省略部分代码

    override fun initView() {

        // 点击头像
        mIvPortrait.setOnClickListener {
            // 调用 登录
            UserContext.login(this) 
        }
        
        // 点击收藏按钮
        mBtnCollect.setOnClickListener {
            // 调用 收藏
            UserContext.collect(this) {
                viewModel.collect(articleId)
            }
        }
        
        mBtnShare.setOnClickListener {
            // 调用 分享
            UserContext.share(this) {
                viewModel.share(articleId)
            }
        }
    }

    ...省略部分代码
}

在 MainActivity中,我们可以优雅实现登录收藏分享功能,在也不用 if-else 判断。

至于切换状态,在登录成功回调中,或者是 退出登录中,可以调用 UserContext 进行状态切换即可。是如下代码:

// 点击退出登录 按钮
mBtnLogout.setOnClickListener {
    // 设置当前状态 为 未登录状态
    UserContext.setLogoutState()
}
// 登录成功 回调
private fun loginSuccess() {
    // 设置当前状态为  登录状态
    UserContext.setLoginState()
}

切换了状态,对于 MainActivity 而言,无需做任何修改,但其里面的行为却发生了变化。

最后

至此,这是一个完整的状态模式实现,从一开始没有使用设计模式,代码中充满了 if-else 的逻辑判断,缺少美感,到后面使用设计模式,前后对比,虽然 类有所增加类 ,但是把烦琐的状态,转换成结构清晰的状态类族,从而避免重复代码,也保证了可扩展性和可维护性,用心感受——状态模式 的魅力吧~

还不赶紧用起来? :)

最后的最后

说到这里,肯定会有 机智 的小伙伴想到,如果我封装成一个通用方法,效果不也是一样吗?

代码如下:

object UserContext{

     // 持久化存储 登录状态
    private var isLogin: Boolean by Preference(Key.LOGIN, false)
    
    private fun execute(context: Context, block: () -> Unit) {
        if (isLogin) {
            block()
        } else {
            startActivity<LoginActivity>()
        }
    }
}

乍一看,好像是这么回事,效果一样,而且少创建一些类。

但是仅仅局限于两种用户状态—— 登录未登录

倘若,在增加一个用户状态,一个只有管理员才能分享的功能呢?

代码最终会变成如下:


object UserContext{

     // 持久化存储 登录状态
    private var isLogin: Boolean by Preference(Key.LOGIN, false)
    
    private fun execute(context: Context, block: () -> Unit) {
        if (isLogin) {
            block()
        } else {
            startActivity<LoginActivity>()
        }
    }
    
    private fun adminExecute(context: Context, block: () -> Unit) {
        if (isLogin) {
    
            if (isAdmin) {
                block()
                return
            }
    
            toast("无法分享,没有对应权限")
        } else {
            startActivity<LoginActivity>()
        }
    }
}

代码上又增加多一条 if-else ,我好像嗅到了代码的坏味道 :)

反观状态模式,不仅消除了 if-else逻辑,结构更为清晰,也使得这模块更加灵活

只需要在新建一个状态类,AdminState 即可~

孰好孰坏? 一看便知~