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