大话设计模式:策略模式
策略模式:
策略模式--我理解的是将不变的东西提取出来,将变化的东西放在具体实现里,通过对具体实现的操作来实现扩展操作。
github地址:https://github.com/iearl/gof23/
例如:电影的票价对于不同用户的价格是不同的,可能存在学生票、会员票和普通票等,可以将计算价格的方法放到一个类中,专门设计一个类来计算票价,通过调用这个方法就可能计算不同角色对应得票价是多少。
/** * 观影类型不同得电影票价 */ public class MovieTicket { private String type;//观影类型 private double price;//票价 public void setType(String type) { this.type = type; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } /** * 放回当前观影人类型对应得票价: * 学生票价八折 * vip票价七折 * 其余原价 * @return */ public double curTicketPrice(){ if("student".equals(type)){ System.out.println("学生票"); return price*0.8; }else if ("vip".equals(type)){ return price*0.7; }else{ return price; } } @Override public String toString() { return "MovieTicket{" + "type='" + type + '\'' + ", price=" + price + '}'; } }
这样做是最简单得,一个类通过if-else的判断就能完成不同角色对应价格判断(其实这是最简单的简单工厂设计模式,稍后将学习到)。
但这样做有一个问题,当我们想要添加儿童这样的角色应该怎么办,改变现有的类?这违反的OCP即开闭原则。
应该怎么办?这时再看策略模式的定义,我们可以将不变和变化的分别抽取出来
将程序中不变的东西提取出来,置成抽象类或接口,很明显方法中的计算价格的方法抽取出来,抽象成接口或抽象类。
public interface IStrategy { double curTicketPrice(Context context); }
将程序中变化的东西作为接口的实现类,将不同角色对应价格的代码提取出来放在不同类中,子类根据自己情况来实现具体方法。例如儿童票5折。
public class StudentStrategy implements IStrategy { public double curTicketPrice(Context context) { return context.getPrice()*0.8; } }
如果添加儿童票,我们仅需要新增类来实现这个接口就好。不需要再修改原先的代码。
public class ChildStrategy implements IStrategy { public double curTicketPrice(Context context) { return context.getPrice()*0.5; } }
Context context:这时什么东西,再之前通过if-else没见过过这个类,这个类有什么作用呢?我们看一下这个类的结构
public class Context { private IStrategy IStrategy; private double price; public double getMethod() { return IStrategy.curTicketPrice(this); } public Context(IStrategy IStrategy, double price) { this.IStrategy = IStrategy; this.price = price; } public IStrategy getIStrategy() { return IStrategy; } public void setIStrategy(IStrategy IStrategy) { this.IStrategy = IStrategy; } public double getPrice() { return price; } }
可以看待这个类是原先类价格和抽象角色,再这个类中设置。这个类的作用是将价格和类型通过构造器设置进去,通过自身提供的getMethod方法来输出最后的价格。
等等,这个类的具体作用是什么,感觉这个类可以不要,完全可以再客户端设置这些值,为什么单独的抽出来呢?
原因很简单:
在上一篇讲的设计模式六大原则中有一个最小知道原则,一个类应该尽量少的与外界通信,如果必须通信可以通过第三方类,这个context就是第三方类;
这里的context还是单一原则的体现,体现引起类变化的因素只有一个那就是Context类,单一原则可看成定义与使用的分离。
到现在为止,是否对策略模式有些了解呢?现在给出策略模式的准确定义:
策略模式:定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
策略模式角色:
策略接口角色:可以看作是将不变的地方抽取出来形成的抽象类或接口,约定一些策略算法。
策略具体角色:可以看作是将变化提取出来成为称为接口角色的实现类,具体实现策略类的算法。
上下文环境:负责客户端与策略之间的交互,持有策略的引用。
策略模式再说明:
策略模式中的策略类可以看作是具体的if-else里面的操作,只是将相关一类算法实现替换扩展而不会影响以前的代码。
具体代码:再github上,地址:https://github.com/iearl/gof23/tree/master/src/main/java/coms/ants/behavior/strategy/simple
UML图,如下
IStratege:充当的是抽象策略角色,Context充当下上文环境角色,VipStartegy、StudentStrategy和ChildStrategy充当具体策略角色,Main充当客户端角色。
绿色的线表示继承:实心箭头和虚线
白色虚线表示依赖关系:使用关系,表示一个事物使用另外一个事物,特定的事物改变可能会影响使用该事物的其他事物。
白色实线+实心菱形表示组合关系:类整体与部分之间关系,整体对象控制成员对象的生命周期,两个同生共死。Context是整体。
策略模式:扩展 当新增的策略子类比抽象策略类功能多时
一:具体策略类
public class TempIStrategy implements IStrategy { private String account; public TempIStrategy(String account) { this.account = account; } @Override public double curTicketPrice(Context context) { System.out.println("账号是:"+account); return context.getPrice()*0.87; } }
具体做法是,子类在重写接口时,需要在自己策略类在添加有关操作,具体做法是新增子类添加什么就在具体实现类添加什么。
这样做的好处时,只添加具体策略,缺点是和其他策略风格不配。
使用具体策略的代码:https://github.com/iearl/gof23/
二:添加具体的上下文环境
public class TempContext extends Context { private String account; public String getAccount() { return account; } public TempContext(IStrategy iStrategy, double price,String account) { super(iStrategy, price); this.account = account; } }
将新增的账号在具体的上下文环境中添加进去,这样保证策略类一致。
策略模式:在JDK中
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
RejectedExecutionHandler 采用策略模式
具体4个策略类。
策略模式:优缺点
优点:相互替换扩展性好、避免多个if-else判断。
缺点:必须了解具体策略、增加策略类数量、算法在运行时只有一个不能适合多层次。