【一起学系列】之策略模式:好多鸭子啊
普遍定义
定义个一个算法族, 各个算法的实现可以相互替换, 算法的实现和具体的算法使用场景隔离
策略模式的进化
【产品】:我需要一堆鸭子,红色的,绿色的,黑色的,还要会飞!还要会叫!
【开发】:明白,秒秒钟搞定,N种鸭子只要继承了我的Duck类然后重写,就搞定一切!
public abstract class Duck {
/**
* 颜色属性
*/
String color;
/**
* 飞行方法
*/
fly();
/**
* 呱呱叫的方法
*/
quack();
}
Some times later...
【BOSS】:磕大头!你怎么回事!!!你能不能告诉我为什么橡皮鸭子会在天上飞!!!???
【开发】:(不敢说话,只有内心独白:原来无脑继承会出大问题啊... 我是谁... 我在哪... 我该怎么办...)
磕大头的十万个问题
Q1. 以后还有很多奇奇怪怪的情况,谁知道老板哪天要不要让橡皮鸭也飞起来,我该怎么办?
Q2. 只用封装,继承好像不行,好像还有一个多态?
Q3. 我该怎么使用多态呢?
Q4. 我是不是可以把飞这个行为「定义成接口,然后把这个接口...... 放到鸭子基类里面去!!!」
核心代码
public abstract class Duck {
/**
* 飞行行为是动态的,可能会变的,因此抽成多个接口的组合,而不是让Duck类继承
*/
FlyBehavior flyBehavior;
/**
* 每个鸭子的叫声不同,抽象成接口
*/
QuackBehavior quackBehavior;
}
这下子,好像真的就解决了呢,想怎么飞就怎么飞,想怎么叫就怎么叫
遵循的设计原则
- 封装变化
- 多用组合,少用继承
- 针对接口编程,而不是针对实现编程
我之前的理解误区
大家在初学Java的时候,肯定都绕不开设计模式这一Part,那个时候我对策略模式的认知是这样的,核心代码如下:
// 不需要看全貌,就看这一段核心代码
public strategyChoose(String mainName){
switch (name){
case "A":
story = new StoryB(name);
break;
case "B":
story = new StoryA(name);
break;
default:
story = new StoryB(name);
break;
}
}
「以下是错误理解:」
我之前认为,策略模式,顾名思义就是定义不同的策略(即算法),然后动态的调用就好了,比如我定义A,B两种策略,然后通过Switch分支语句选择策略就可以
这样理解的错误点在于:错误的理解了策略模式的重点,而误把工厂模式的策略选择当作策略模式
「现在的理解:」
- 设计模式肯定有其变种,我们不需要太拘泥于条条框框而限制了自我
- 任何定义都有其普遍的理解,我们在突破自我的同时,不能完全走偏
- 所以「真正的策略模式核心即:封装行为,依赖接口,组合代替继承」
为什么策略模式常和工厂模式或枚举混用?
首先我们知道了策略模式最初的起源和关键点:「行为或者说算法的抽象及接口的组合」
那么问题来了,在我们拥有多种策略的时候,策略的数量和类型会大大增加,那么调用N种策略肯定需要一个简单的逻辑进行更好的调用,那么策略枚举,或者简单工程则会成为我们最先考虑的方式之一,也正因为如此,才会有上文中我出现的理解误区
生活中的策略模式
- LOL里我喜欢玩奶妈,因为奶妈可以加血,那么在英雄技能设计上,加血是一种行为,具体的细节每个英雄都不一样,这就是策略模式的一种体现
- 比如支付场景中我们会抽取支付信息中的订单,状态,付款人,金钱等等信息,但是支付的细节可以是支付宝也可以是微信
真假策略模式
写这篇文章的时候,在网上也查询了很多资料,发现大家的策略模式总有一些细枝末节不一样的地方,接下来会展示一部分代码,大家可以主观来判断一下它是否属于策略模式,是否可以用其他设计模式代替
/**
* 网络玄幻小说的故事梗概接口
* 固定的一些套路
* 策略模式:网络小说的固定套路
*/
public interface Synopsis {
/**
* 穷困潦倒的开始
*/
void badStart();
/**
* 突然有天遇到神人/捡到神器,实力大涨
*/
void adventure();
/**
* 在一场战斗中以弱胜强
*/
void winABattle();
/**
* 从此飞速成长
*/
void growFast();
/**
* 组团刷怪,经历九死一生(主角怎么也死不了)
*/
void manyFights();
/**
* 最终功成名就,妻妾成群
*/
void succeed();
/**
* 组合起来就是小说内容
*/
void getContent();
}
/**
* 故事 A ,恶魔法则
*/
public class StoryA implements Synopsis {
// 实现省略
}
/**
* 故事 B ,诛仙
*/
public class StoryB implements Synopsis {
// 实现省略
}
策略选择类:
/**
* 策略选择类
*/
public class WriteNovel {
private Synopsis mSynopsis; //故事梗概
private String mMainActorName; //主角名称
/**
* 梗概、内容都差不多确定后,换个名称就是另一部小说
* @param mainName
*/
public WriteNovel(String mainName){
switch (mainName){
case "张小凡":
mSynopsis = new StoryB(mainName);
break;
case "杜维":
mSynopsis = new StoryA(mainName);
break;
default:
mSynopsis = new StoryB(mainName);
break;
}
}
/**
* 获取小说内容
*/
public void getNovelDetail(){
mSynopsis.getContent();
}
}
最后
附上策略模式的UML图
然后再说一个暖心小故事~
在2020年5月6日的时候,我发现了掘金的一个BUG,整个界面都加载不出来(已经修复啦~)
第二天我找到了首席产品体验官优弧小姐姐,跟她积极反馈了BUG,同时小小的推荐了一下子自己的文章,然后发了许久的一篇文章就这样再一次上了热门第一名,实证如下(开心到爆炸):
- Framework 4.0 新关键字dynamic 之我见(一)
- Timber 经典的android Log封装库
- Butter Knife快速开发
- 如何将Markdown文章轻松地搬运到微信公众号并完美地呈现代码内容
- IoC与AOP的那点事儿
- ossec入侵检测日志行为分析
- 从零开始的Spring Session(三)
- 从零开始的Spring Session(一)
- 一个通用的Java正则匹配工具
- 从零开始的Spring Session(二)
- [汇总]2013年度全球重、特大网络安全事件回顾
- android常用接口(一)
- 2014密码时代已死?六种旨在取代传统密码位置的新奇想法
- 程序员你为什么这么累【续】:编码习惯之配置规范
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Django+Vue开发生鲜电商平台之7.用户登录和注册功能
- centos7下nginx使用tmp目录问题
- TeaWeb – 可视化的Web代理服务
- Slf4j适配日志原理
- vim编辑器
- 深度学习常用Linux命令
- Vlan 和 和 Trunk 实验
- koa框架生成微信公众号二维码
- KVM 实现虚拟机在线热迁移
- 机器重启后 docker 容器启动报错 error creating overlay mount t
- 微信小程序开发实战(14):音频组件(audio)
- dotnet OpenXML 文本 BodyProperties 的属性作用
- 微信小程序开发实战(15):视频组件(video)
- LeetCode 91,点赞和反对五五开,这题是好是坏由你来评判
- 打破国外垄断,开发中国人自己的编程语言(1):编写解析表达式的计算器