前言
相信 “大多数” 伙伴看了相关设计模式
书籍、文章,一看就会,一写就 ***
在实际开发中也很少用上,或者说不知道在哪用,缺少应用场景
,久而久之就忘记了~
在这篇文章中,我分享一下,我在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
接口类,结合上面背景,这里定义 login
、collect
、share
方法
(行为),然后我们分别创建 登录状态
(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
即可~
孰好孰坏? 一看便知~