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

前言

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

在这篇文章中,我分享一下,我在Android 开发中使用到到设计模式——状态模式,但愿你们喜欢~ide

需求

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

当用户点击头像的时候ui

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

编码

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

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 变量,而后根据该变量,来判断是否进行登陆页面跳转。spa

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

扩展需求

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

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

编码

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 LoginState : 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 便可~

孰好孰坏? 一看便知~

相关文章
相关标签/搜索