Android设计模式实战-使用策略模式封装 '并且' 与 '或者' 的逻辑

1,141 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情

前言

平常我们在开发应用的过程中我们大多数情况下都是 if(){} 。稍微复杂一点的条件我们也可以使用 if(a && b){} 或者 if(a || b){}

当a和b的判断条件都满足,或者只有一个条件满足,则运行到制定逻辑。

那么如果现实的场景是这样的,判断条件很多,并且灵活的变换,那么我们这么硬编码写死的情况会不会改起来很麻烦呢?

以之前遇到的一个情况为例,场景如下:

比如我需要申请一个工作,那么在调用接口之后我需要校验一些条件

  1. 是否有新冠报告
  2. 工作时间是否符合
  3. 是否是VIP客户
  4. 用户的国籍
  5. 用户的护照状态
  6. 身份证号码判断是否是学生
  7. 年龄是否满足工作需求
  8. 语言是否满足工作需求
  9. 性别是否满足工作需求
  10. 是否已经晚上了详细信息
  11. 是否需要卫生许可证
  12. 是需要完成了在线培训
  13. 工作是否需要押金
  14. 是否是自营工作
  15. 是否是Vip顾客等
  16. 等等

多的不说,假如就只有就15种状态判断,我们if else if 写完了,好了,现在逻辑变了,只是第一个条件变了,现在不需要新冠报告了,我们就需要修改if else的顺序。如果是并且的逻辑变了,或者的逻辑变了,那么也要到处找if esle的逻辑去修改。

这种情况下,有没有一种可能,我们把逻辑判断封装为一个对象,然后通过一个执行器来执行判断的逻辑,就可以很方便的修改判断的逻辑的变动。

我们之前讲过策略模式,我们把判断的逻辑封装为接口,通过实现不同的接口完成对应逻辑的判断,然后再执行器中执行这些逻辑。

基类的封装

定义一个策略接口

interface BaseRule<T> {

    fun execute(rule: T): RuleResult
}

而我们的规则执行器就可以定义一些策略的执行规则

class RuleExecute<T>(private val check: T, private val hashMap: MutableMap<String, List<BaseRule<T>>>) {

    companion object {

        @JvmStatic
        fun <T> create(check: T): Builder<T> {
            return Builder(check)
        }

        private const val AND = "&&"
        private const val OR = "||"
    }


    // 构建者模式。
    open class Builder<T>(private val check: T) {

        private val hashMap: MutableMap<String, List<BaseRule<T>>> = HashMap()
        var indexSuffix = 0

        //并且的逻辑
        fun and(ruleList: List<BaseRule<T>>): Builder<T> {
            val key = AND + (indexSuffix++).toString()
            hashMap[key] = ruleList
            return this
        }

        //或者的逻辑
        fun or(ruleList: List<BaseRule<T>>): Builder<T> {
            val key = OR + (indexSuffix++).toString()
            hashMap[key] = ruleList
            return this
        }


        fun build(): RuleExecute<T> {
            return RuleExecute(check, hashMap)
        }

    }

    //执行任务
    fun execute(): RuleResult {
        for ((key, ruleList) in hashMap) {
            when (key.substring(0, 2)) {
                AND -> {
                    val result = and(check, ruleList)
                    if (!result.success) {
                        return result
                    }
                }
                OR -> {
                    val result = or(check, ruleList)
                    if (!result.success) {
                        return result
                    }
                }
            }
        }
        return RuleResult(true)
    }

    private fun and(check: T, ruleList: List<BaseRule<T>>): RuleResult {
        for (rule in ruleList) {
            val execute = rule.execute(check)
            if (!execute.success) {
                //失败一次就要return
                return execute
            }
        }
        // 全部匹配成功,返回 true
        return RuleResult(true)
    }

    private fun or(check: T, ruleList: List<BaseRule<T>>): RuleResult {
        val stringBuilder = StringBuilder()

        for (rule in ruleList) {
            val execute = rule.execute(check)
            if (execute.success) {
                // 从前往后遍历,只要一个满足条件就return
                return RuleResult(true)
            } else {
                stringBuilder.append(execute.message)
            }
        }

        // 一个都匹配不到,才返回false
        val result = RuleResult(false, stringBuilder.toString())
        stringBuilder.clear()
        return result
    }

}

内部使用构建者模式构建对象,支持多次的and 和 or。整体的流程其实也是一个and,当一个执行者方法走完如果有错误就不会继续执行了。

其中自定义返回的结果对象为

data class RuleResult(val success: Boolean, val message: String = "")

T则是我们的泛型入参,不同的场景使用不同的类型。

定义不同的判断器

下面我们开始定义执行场景下的一些判断逻辑。

假如我们的逻辑如下:(伪代码)


if(hasCovidTest){

  if(nationality || visa){

   if(!isVip && fillProfile){
 
      if(gender && language && age){

          if(disposit && isSelfProvide){
               
               if(isTrained){
                   applyJob()
               }
          }
      }else{
        toast("xxx)
      }

   }else{
     gotoProfilePage()
   }

  }else{
    showErrorPopuowindow()
  }

}else{
    showDialogTips()
}

我们使用对象封装的方式来定义一些规则器:

先定义一个校验对象,一般由服务器返回

data class JobCheck(
    //人物要求
    val nric: String,
    val visa: String,
    val nationality: String,
    val age: Float,
    val workingYear: Float,
    val isFillProfile: Boolean,

    val hasCovidTest: Boolean,

    //工作要求
    val language: String,
    val gender: Int,
    val deposit: String,
    val isSelfProvide: Boolean,
    val isTrained: Boolean,
)

比如我们先判断是否有新冠检测报告,那么我们定义一个规则对象,由于失败要弹窗,所以我们定义一个失败回调。

class COVIDRule(private val callback: () -> Unit) : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (rule.hasCovidTest) {
            RuleResult(true)
        } else {
            callback()
            RuleResult(false, "你没有新冠检测报告")
        }
    }

}

如果不想要回调,我们则可以很简单的定义一些规则:

class AgeRule : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (rule.age > 16 && rule.age < 50) {
            RuleResult(true)
        } else {
            RuleResult(false, "年龄不满足")
        }

    }
    
}

class FillProfileRule : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (rule.isFillProfile) {
            RuleResult(true)
        } else {
            RuleResult(false, "请完善用户详细信息")
        }

    }
    
}

class GenderRule(private val requirGender: Int) : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (requirGender == 2) {
            RuleResult(true)
        } else {

            RuleResult(
                rule.gender == requirGender,
                if (rule.gender == requirGender) "" else "性别不符合此工作"
            )

        }
        
    }

}

class LanguageRule(private val requirLanguages: List<String>) :BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (requirLanguages.contains(rule.language)) {
            RuleResult(true)
        } else {
            RuleResult(false, "语言不符合此工作")
        }

    }

}

class NationalityRule(private val requirNationalitys: List<String>) : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (requirNationalitys.contains(rule.nationality)) {
            RuleResult(true)
        } else {
            RuleResult(false, "国籍不符合此工作")
        }

    }

}

class VisaRule : BaseRule<JobCheck> {

    override fun execute(rule: JobCheck): RuleResult {

        return if (rule.visa.lowercase() == "Singapore".lowercase()) {
            RuleResult(true)
        } else {
            RuleResult(false, "签证不满足工作需求")
        }


    }

}

当然这都是Demo级别的,我们做的最简单的示例,真实逻辑是需要在里面判断自定义的逻辑的,比如 age 要求18,19, 20 然后 25 -35 然后小于50岁,等等这些都需要在 AgeRule 对象中精准的校验。根据工作的具体要求然后做具体的比对。

使用

在使用的使用我们就需要先封装好检测的对象,一般是由服务器返回,和工作的要求等等信息的一些组合。

我们这里Demo就最简单的new一个对象 然后我们初始化

    val jobCheck = JobCheck(
                "S9876543A", "Singapore", "Singapore", 20F,
                3.5F, false, false,
                "Chinese", 1, "25", true,
                false
            )

然后我们初始化制定的一些规则对象,使用规则执行器来执行。

    val covidRule = COVIDRule {
        //如果不满足新冠,首先就被排除了
        //如果没有新冠 - 弹窗提示他
        FangIOSDialog(mActivity).apply {
            setTitle("你没有新冠表单")
            setMessage("老哥你快去做核酸吧,老哥你快去做核酸吧,老哥你快去做核酸吧")
            setPositiveButton("好的") {
                dismiss()
            }
            setNegativeButton("就不去") {
                dismiss()
            }
            show()
        }

    }  //false
    val ageRule = AgeRule()  //true
    val fillProfileRule = FillProfileRule()  //true
    val genderRule = GenderRule(2)  //false
    val languageRule = LanguageRule(listOf("Chinese", "English"))   //true
    val nationalityRule = NationalityRule(listOf("China", "Malaysia"))   //true
    val visaRule = VisaRule()   //true

    //构建规则执行器
    val result = RuleExecute.create(jobCheck)
        .and(listOf(covidRule))
        .or(listOf(nationalityRule, visaRule))
        .and(listOf(ageRule, fillProfileRule, genderRule, languageRule))
        .build()
        .execute()

    YYLogUtils.w("执行规则器的结果:$result")

可以看到我们先添加的新冠校验,直接就不通过,后面的就不会走,直接走到else中弹窗的逻辑

image.png

那么我们手动的把新冠测试通过

 jobCheck.hasCovidTest = true

在运行看看:

image.png

第一个and 第二个or 都通过了,走到第三个and中的用户信息没有完善,如果需要去跳转到用户信息页面,那么我们和新冠的Rule一样,在 FillProfileRule 的规则中加入失败的回调即可。

如果想在某一个节点的成功之后做一些别的逻辑,也可以在指定的Rule或and方法加上一些成功的回调,这些都不难,大家如果有兴趣也可以自行扩展。

总结

我们通过策略模式加上构建者模式实现的封装框架。如果在一些判断条件很多并且可能随时变化的一些场景中,这样的封装是很实用的,修改起来也是非常的快捷。

在一些购物商场的一些场景中也会用到,判断用户和商品,商家,平台的一些活动,折扣等一些较为复杂的判断场景,使用这样的封装会更加方便一点。

本文全部代码都已全部贴出,如果大家有兴趣也可以查看源码自取。

好了,本期内容如讲的不到位或错漏的地方,希望同学们可以指出交流。

如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。

Ok,这一期就此完结。