本篇语言使用kotlin
为什么使用策略模式
按照个人理解,策略模式可以避免掉大部分switch语句和if esle语句。在之前遇到的项目中,有很多同学在对同一个参数,对应不同处理方式的情形下,多会采用大量的if else,这样的代码也可以运行,但是在后期的维护中,会非常难理解。(其实使用策略模式也会有类膨胀的问题,但也有优化空间,而且个人认为要比上千行的if else好一些)
根据开闭原则,在新增代码逻辑时,优先考虑新增扩展子类,而不是修改原有的类。
在实际项目中,我有遇到过数据库操作的情形,在安卓app中使用room库,需要操作不同的数据表,但是行为都是增删查改,此处可以考虑使用策略模式,横向拓展操作数据表类。以下使用相似的情景代替。
什么是策略模式
面向过程设计
companion object{
const val APPLE = 0
const val ORANGE = 1
const val GRAPE = 2
}
fun eatFruit(fruitType : Int){
if (fruitType == APPLE){
pareByKnife()
}else if (fruitType == ORANGE){
pareByHand()
} else if (fruitType == GRAPE){
withoutPare()
}
}
//或者使用
fun eatFruit(fruitType : Int){
when (fruitType) {
APPLE -> {
pareByKnife()
}
ORANGE -> {
pareByHand()
}
GRAPE -> {
withoutPare()
}
}
}
fun pareByKnife(){
}
fun pareByHand(){
}
fun withoutPare(){
}
在分支数量较少时,采用这种写法是可以接受的。优化一下,使其满足开闭原则。
下面使用策略模式,面向对象设计
class EatFruitContext (eatFruit: EatFruit){
private var eatFruit : EatFruit = eatFruit
public fun getWayToEat(){
eatFruit.howToEat()
}
}
open class EatFruit {
public open fun howToEat(){
}
}
class EatApple : EatFruit() {
override fun howToEat() {
super.howToEat()
pareByKnife()
}
fun pareByKnife(){
}
}
class EatGrape : EatFruit() {
override fun howToEat() {
super.howToEat()
withoutPare()
}
fun withoutPare(){
}
}
class EatOrange : EatFruit() {
override fun howToEat() {
super.howToEat()
pareByHand()
}
fun pareByHand(){
}
}
策略模式.png
使用了策略模式之后,虽然满足了开闭原则,但是在几百条不同路径时,会创建非常多的子类,这样会导致类膨胀,反而不利于代码的理解,其实也有优化的思路,(之前遇到过这样的项目,在接收到网络消息后,需要根据协议解析数据,走不同的代码块,协议拓展到上百条后,if else代码会显得非常臃肿,上千行的if else绝对是噩梦级代码,相比于此情形,类膨胀要稍显柔和一些)一种是使用java的函数式接口,另一种是使用反射,反射可以在程序运行时修改属性,是java解释型语言的一种特性。
策略模式的缺陷
优化类膨胀
1.使用函数作为参数的思路
主要思想是,使用HashMap存储下所有的策略,在请求策略时,从map中取出,可以使用字符串或者Int代替下方代码中的EatFruit类的子类对象。
class EatFruitContext (private val eatFruit: EatFruit){
private val eatApple = EatApple()
private val eatOrange = EatOrange()
private val eatGrape = EatGrape()
private var functionList : HashMap<EatFruit, () -> Unit> = HashMap()//
init {
functionList[eatApple] = { eatApple.howToEat() }
functionList[eatOrange] = { eatOrange.howToEat() }
functionList[eatGrape] = { eatGrape.howToEat() }
}
public fun getWayToEat() : (() -> Unit)? {
return functionList[eatFruit]
}
}
class EatFruitContext (private val eatFruit: Int){
private val eatApple = 0
private val eatOrange = 1
private val eatGrape = 2
private var functionList : HashMap<Int, () -> Unit> = HashMap()
init {
functionList[eatApple] = { //TODO EAT
}
functionList[eatOrange] = { //TODO EAT
}
functionList[eatGrape] = { //TODO EAT
}
}
public fun getWayToEat() : (() -> Unit)? {
return functionList[eatFruit]
}
}
java代码可以参考>https://www.csdn.net/tags/Mtjacg4sMDA5MzMtYmxvZwO0O0OO0O0O.html
2.使用反射的思路
结合抽象工厂模式思路会更清晰(之前有个项目就是用反射实例化各个窗口跳转按钮,在配置文件中确定哪些按钮需要实例化)
1.新建xml或者其他类型配置文件,运行时读取配置属性,达到自动实例化对应类的目的(读取xml的ConfigurationManager类在此不贴出来了)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<EatFruitSettings>
<add key="FruitType" value="apple"/>
<add key="FruitType" value="orange"/>
<add key="FruitType" value="grape"/>
</EatFruitSettings>
</configuration>
class EatFruitContext2 (private val className: String){
private var functionList : HashMap<String, () -> Unit> = HashMap()
init {
for(string in ConfigurationManager.appSetting.keys){
functionList[string] = {
(Class.forName(string).kotlin as EatFruit).howToEat()
}
}
}
public fun getWayToEat() : (() -> Unit)? {
return functionList[className]
}
}
2.通过遍历查找到继承EatFruit接口或基类的所有实现类或子类,达到自动实例化对应类的目的
思路和上面类似,遍历实现类或子类的代码就不贴了
策略模式-菜鸟教程













网友评论