设计模式之策略模式及模板模式
策略模式:
其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
角色组成:
—抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
—具体策略角色:包装了相关的算法和行为。
—环境角色:持有一个策略类的引用,最终给客户端调用。
在我们网上购物的时候我们可以选择多种支付方式,包括支付宝,微信,京东,银联卡等等的支付渠道,这里就以这个例子演示一下策略模式的使用:
抽象策略角色,提供一个支付方法:
public interface Payment { // 定义支付方法 String pay(); }
具体策略角色,具体执行算法的类,这里我定义了3种支付策略:
public class AliPay implements Payment { @Override public String pay() { System.out.println("欢迎使用支付宝"); System.out.println("查询账户余额,开始扣款"); return "支付成功"; } } public class JDPay implements Payment { @Override public String pay() { System.out.println("欢迎使用京东白条"); System.out.println("查询账户余额,开始扣款"); return "支付成功"; } } public class UnionPay implements Payment { @Override public String pay() { System.out.println("欢迎使用银联卡支付"); System.out.println("查询账户余额,开始扣款"); return "支付成功"; } }
环境角色:持有一个策略类的引用,最终给客户端调用。这里环境角色就设置为收银台。
public class Checkstand { //这个参数,完全可以用Payment这个接口来代替 //完美地解决了switch的过程,不需要在代码逻辑中写switch了 //更不需要写if else if public String pay(PayType payType){ return payType.get().pay(); } }
这里的参数就是我们上面写的抽象策略角色,即接口。我直接定义了一个方法做个简单演示。然后采用了枚举类的方式来生成指定策略实例:
public enum PayType { ALI_PAY(new AliPay()), UNION_PAY(new UnionPay()), JD_PAY(new JDPay()); private Payment payment; PayType(Payment payment){ this.payment = payment; } public Payment get(){ return this.payment;} }
测试:运行以下代码能看到由我们的选择去做底层的策略
public class PayStrategyTest { public static void main(String[] args) { //省略把商品添加到购物车,再从购物车下单 //直接从支付开始 Checkstand checkstand= new Checkstand(); //开始支付,选择微信支付、支付宝、银联卡、京东白条、财付通 //每个渠道它支付的具体算法是不一样的 //基本算法固定的 //这个值是在支付的时候才决定用哪个值 System.out.println(checkstand.pay(PayType.ALI_PAY)); } }
工厂模式和策略模式看着很像,它们的区别在哪里,需要细细体味。
用途不一样 :
工厂是创建型模式,它的作用就是创建对象;
策略是行为型模式,它的作用是让一个对象在许多行为中选择一种行为;
关注点不一样 :
一个关注对象创建 一个关注行为的封装
解决不同的问题 :
工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关。主要应用在多数据库选择,类库文件加载等。
策略模式是为了解决的是策略的切换与扩展,更简洁的说是定义策略族,分别封装起来,让他们之间可以相互替换,策略模式让策略的变化独立于使用策略的客户。
工厂相当于黑盒子,策略相当于白盒子;
模板模式:
在模板方法模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
这里就不自己写实例了 ,来看一下 Dubbo 中 负载均衡算法的实现:
AbstractLoadBalance.select(List<Invoker<T>> invokers, URL url, Invocation invocation):
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) { if (invokers == null || invokers.size() == 0) return null; if (invokers.size() == 1) return invokers.get(0); return doSelect(invokers, url, invocation); } protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
可以看到,在select方法中先是检查 List 是否为空,长度,最后才执行最核心的负载算法方法,doSelect,但是在本类中仅仅是定义了一个抽象定义,具体的实现逻辑由4哥子类去实现,在最后的调用链中,不管是哪个子类去执行,都要经过前面的两个步骤,最后才到自己的实现。这就是模板方法。
策略模式:只有选择权,由用户自己选择已有的固定算法
模板方法:侧重点不是选择,也没得选,必须遵循原来的业务逻辑,就好比上面的例子,必须要先执行前面的判断,最后才能到自己的实现中,仅仅定义一部分。