设计模式之策略模式,来源于生活,用之于生活
为什么需要设计模式?
设计模式是软件设计中常见问题的通用可重用的解决方案,与语言无关。通过引入设计模式,可以更好的提高代码复用性、灵活性、扩展性。
程序设计原则
程序设计也需要遵循很多原则,开闭原则就是说对扩展开放,对修改关闭。里氏代换原则,任何基类可以出现的地方,子类一定可以出现。依赖倒转原则、接口隔离原则、迪米特法则、合成复用原则。
设计模式的分类
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
策略模式
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户,属于行为型模式。
使用场景
电商网站支付方式,一般分为银联、微信、支付宝,可以采用策略模式,为每一种方式,作为一种支付方式的实现,如果哪一个支付方式方式,发生了变化,只要修改对应的实现即可,不需要修改调用的客户端,如果引入新的支付,如万里通支付,增加一个对应的万里通支付实现类,遵循了开闭原则。
dubbo,spring cloud Ribbon客户端负载均衡策略,都是采用的策略模式,dubbo有一致性hash均衡策略、最小活跃调用书均衡策略、随机均衡策略、轮询均衡策略。
RoundRobinRule 轮询 RandomRule 随机
AvailabilityFilteringRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数超过阈值的服务,然后对剩余的服务列表进行轮询
WeightedResponseTimeRule 权重根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时,如果统计信息不足,则使用轮询策略,等信息足够,切换到 WeightedResponseTimeRule
RetryRule 重试 先按照轮询策略获取服务,如果获取失败则在指定时间内重试,获取可用服务
BestAvailableRule 选过滤掉多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule 符合判断server所在区域的性能和server的可用性选择服务
计算器的计算方式,加减乘除方法的实现也可以采用策略模式,如果加入一种新的计算取余,增加一种实现即可。
具体实现:
//糟糕的代码
publicclassBadCode{
privatedoublenum1;
privatedoublenum2;
privateString method;
publicBadCode(doublenum1,doublenum2, String method) {
this.num1 = num1;
this.num2 = num2;
this.method = method;
}
publicdoublecalculate() {
if("+".equals(this.method)){
returnnum1 + num2;
}elseif("-".equals(this.method)){
returnnum1 - num2;
}elseif("*".equals(this.method)){
returnnum1 * num2;
}elseif("/".equals(this.method)){
returnnum1/num2;
}else{
thrownewRuntimeException("method error");
}
}
}
如果新加一种运算方法,需要修改calculate方法,增加一个else if 判断条件,代码的扩展性比较差,如果使用策略模式,可以改善这种情况,将容易修改的地方分离,便于扩展自由动态的选择。
定义计算接口
public interface Calculator {
public double calculate(double num1,double num2);
}
//加法实现
publicclassOptionAddimplementsCalculator{
publicdoublecalculate(doublenum1,doublenum2){
returnnum1 + num2;
}
}
//减法实现
publicclassOptionSubstractimplementsCalculator{
publicdoublecalculate(doublenum1,doublenum2){
returnnum1 - num2;
}
}
//乘法实现
publicclassOptionMultiplyimplementsCalculator{
publicdoublecalculate(doublenum1,doublenum2){
returnnum1 * num2;
}
}
//除法实现
publicclassOptionDivideimplementsCalculator{
publicdoublecalculate(doublenum1,doublenum2){
returnnum1 / num2;
}
}
//定义计算
publicclassContext{
privateCalculator calculator;
publicContext(Calculator calculator){
this.calculator = calculator;
}
publicdoubleexecuteStrategy(intnum1,intnum2){
returncalculator.calculate(num1, num2);
}
publicCalculatorgetCalculator(){
returncalculator;
}
publicvoidsetCalculator(Calculator calculator){
this.calculator = calculator;
}
}
//测试类
publicclassTest{
publicstaticvoidmain(String[] args){
Context context =newContext(newOptionAdd());
doubleresult = context.executeStrategy(1,3);
System.out.println("add:"+ result);
context.setCalculator(newOptionSubstract());
result = context.executeStrategy(1,3);
System.out.println("substract:"+ result);
context.setCalculator(newOptionMultiply());
result = context.executeStrategy(1,3);
System.out.println("multiply:"+ result);
context.setCalculator(newOptionDivide());
result = context.executeStrategy(1,3);
System.out.println("divide:"+ result);
}
}
测试结果
上面只是简单的实现,实际应用开发过程中,需要和spring配合使用,将算法实现交给spring容器进行管理,客户端通过spring注入相应的bean,而不是像测试类一样,每次都创建一个实例。
策略模式的优缺点
优点:算法可以自由切换 避免使用多重条件判断 扩展性良好
缺点:策略类会增多 所有策略类都需要对外暴露
生活中的策略模式
去往某一个目的地,短距离可以选择步行、共享单车,中等距离可选择打车、地铁,长距离可选择高铁、飞机。不是一成不变的,可以自由切换。上班距离很短,平时可能步行,共享单车,如果哪一天起床晚了,赶时间,可以切换到打车模式。
我的启发
策略模式,就像一个可插拔的组件,就像电脑一样,如果硬盘坏了,我只要换一个硬盘而不是整个电脑,我可以选择不同的品牌,我也可以选择是机械的还是固态的,我可以使用不同的策略,因为所有的厂商,都遵循同一套标准,使用策略模式,能够拥有更好的灵活性。一架飞机,出现问题,通过系统检测,如果发动机有问题,把整个发动机进行替换,对于出现故障的发动机,再进行内部结构进行检测,使用同样的方法,进行检测和替换,直到找到真正的原因。设计模式来源于生活,使用于生活。