策略模式
定义:定义了算法族,分别将他们封装起来,让它们之间能相互替换。此模式让算法独立于使用算法的用户。
类图
下面我们用两个列子来说明策略模式的实际用途。
1. 从上班开始
有一个程序员类,他有一个名称,然后有一个上班的方法:
//程序员类 public class Programmer { //姓名 private String name; //getter & setter public String getName() { return name; } public void setName(String name) { this.name = name; } // 构造方法 public Programmer(String name) { this.name = name; } //上班的方法 public void GoToWork(){ System.out.println("坐地铁上班!"); } }
如果,我们的同事上班方式有所不同,有的人坐地铁,有的人做公交车,有的人骑自行车…那么这个类明显不能满足我们的要求,怎么办?有的人想到了继承,就是创造一个坐公交的程序员,让那些坐公交的人继承程序员类,然后重写GoToWork()方法,这个方法是解决了问题,可是我们想想,如果程序员有任何变化,增加或者修改某个属性或者方法,那么就得对子类进行修改,这是一个不能接受的结果?有更优雅的方式吗?有的人想到了接口,就是将上班抽象成接口,然后让不同上班方式去继承接口,再让他们继承程序员类,并且去实现上班接口
这样是将上班行为分离出来了,但是如果增加了步行上班的人、开车上班的人…..等等,代码越来越多了,而且总是要去继承程序员类,实现上班接口,而且有一天,哪个坐地铁的人买了车,改为开车上班呢?是不是的去改代码?没法动态改变?还有没有更优越的方式呢?
是的,用策略模式~!就是将上班的方式改为一个接口,并且以组合的方式放在程序员的内部,然后让各种上班方式去实现这个上班的接口,程序员上班时,动态传入上班的工具,调用接口中的上班方法就可以了。程序员类不需要知道通过什么方式上班,上班的人自己确定通过哪种方式上班就行了:
public interface GoToWork { void goToWork(String name); }
//坐地铁上班
public class SubwayToWork implements GoToWork { // 坐地铁上班 public void goToWork(String name) { System.out.println(name + "坐地铁上班"); } }
//坐公交上班 public class BusToWork implements GoToWork { public void goToWork(String name) { System.out.println(name + "坐公交上班"); } }
//开车上班
public class DriveToWork implements GoToWork { public void goToWork(String name) { System.out.println(name + "开车上班"); } }
//程序员类
public class Programmer { //姓名 private String name; //将上班方法变成一个接口 private GoToWork goToWork; // 构造方法 public Programmer(GoToWork goToWork) { this.goToWork = goToWork; } //上班的方法 public void goToWork(String name){ goToWork.goToWork(name); } //getter & setter public String getName() { return name; } public void setName(String name) { this.name = name; } public GoToWork getGoToWork() { return goToWork; } public void setGoToWork(GoToWork goToWork) { this.goToWork = goToWork; } }
//测试程序员上班
public class ProgrammerTest { public static void main(String[] args) { //坐地铁上班方式上班 SubwayToWork subway = new SubwayToWork(); Programmer p = new Programmer(subway); p.goToWork("小明"); // 坐公交上班方式上班 BusToWork bus = new BusToWork(); p.setGoToWork(bus); p.goToWork("小红"); // 有一天小明买车了 DriveToWork drive = new DriveToWork(); p.setGoToWork(drive); p.goToWork("小明"); } }
如果哪天增加了一个骑自行车上班的人,那么,只需要去继承GoToWork接口,实现goToWork方法,就行了,不需要改动程序员类,程序员类也不用知道有没有骑自行车上班这个方法。充分解耦了程序员类与上班方式之间的关系。
//结账类 public class CheckOut { private Commodity commodity; //商品 private String discount; //折扣方式 //构造方法 public CheckOut(Commodity commodity,String discount) { this.commodity = commodity; this.discount = discount; } //结账方法 public double checkOut(){ if(this.discount.equals("打八折")){ return this.commodity.total() * 0.8; }else if(this.discount.equals("打五折")){ return this.commodity.total() * 0.5; }else if(this.discount.equals("满500减200")){ int i = (int)this.commodity.total()/500; return (this.commodity.total() - (i * 200)); }else{ return 0; } } //getter & setter public Commodity getCommodity() { return commodity; } public void setCommodity(Commodity commodity) { this.commodity = commodity; } public String getDiscount() { return discount; } public void setDiscount(String discount) { this.discount = discount; } }
//商品类 public class Commodity { //价格 private double price; //数量 private double quantity; //总价 public double total(){ return this.price * this.quantity; } //getter & setter public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getQuantity() { return quantity; } public void setQuantity(double quantity) { this.quantity = quantity; } }
//测试结账 public class CheckOutTest { public static void main(String[] args) { // 商品类 Commodity c = new Commodity(); c.setPrice(500); c.setQuantity(3); // 根据购买的商品进行结账 CheckOut checkOut = new CheckOut(c, "满500减200"); System.out.println("您需要付:"+ checkOut.checkOut()); } }
看到这段代码,然后想想之前的代码,根据设计的原则,也该将结账类中的checkOut结账打折方法独立出来,这是变化的部分,结账也该和打折分开。我们收款时,只需要传入相关的策略,就能得到对应的结果。
我们先看看类图
//结账类 public class CheckOut { //收银接口 private MakeCollection makeCollection; //Commodity private Commodity commodity; //构造方法 public CheckOut(MakeCollection makeCollection,Commodity commodity) { this.makeCollection = makeCollection; this.commodity = commodity; } //结账方法 public double makeCollection(){ return makeCollection.makecollection(commodity); } //getter & setter public Commodity getCommodity() { return commodity; } public void setCommodity(Commodity commodity) { this.commodity = commodity; } public MakeCollection getMakeCollection() { return makeCollection; } public void setMakeCollection(MakeCollection makeCollection) { this.makeCollection = makeCollection; }
//打折策略接口 public interface MakeCollection { //根据打折类型收款 double makecollection(Commodity commodity); }
//默认不打折 public class Default implements MakeCollection { public double makecollection(Commodity commodity) { return commodity.total(); } }
//八折策略 public class Discount8 implements MakeCollection { public double makecollection(Commodity commodity) { return commodity.total() * 0.8; } }
//打五折策略 public class Half implements MakeCollection { //打五折 public double makecollection(Commodity commodity) { return commodity.total() * 0.5; } }
//满500减200策略
public class Full500by200 implements MakeCollection { public double makecollection(Commodity commodity) { int i = (int)commodity.total() / 500; return (commodity.total() - (200 * i)); } }
//商品类 public class Commodity { //价格 private double price; //数量 private double quantity; //总价 public double total(){ return this.price * this.quantity; } /** * 构造方法 * @param price 价格 * @param quantity 数量 */ public Commodity(double price, double quantity) { this.price = price; this.quantity = quantity; } //getter & setter public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getQuantity() { return quantity; } public void setQuantity(double quantity) { this.quantity = quantity; } }
//收款测试类
public class CheckOutTest { public static void main(String[] args) { // 商品类 Commodity c = new Commodity(500,3); // 选择默认策略 CheckOut checkOut = new CheckOut(new Default(),c); System.out.println("默认时,您需要付:"+ checkOut.makeCollection()); // 选择满500减200策略 checkOut.setMakeCollection(new Full500by200()); System.out.println("满500减200时,您需要付:"+ checkOut.makeCollection()); //选择打八折策略 checkOut.setMakeCollection(new Discount8()); System.out.println("打八折时,您需要付:"+ checkOut.makeCollection()); // 选择打对折策略 checkOut.setMakeCollection(new Half()); System.out.println("打对折时,您需要付:"+ checkOut.makeCollection()); } }
//运行结果
默认时,您需要付:1500.0 满500减200时,您需要付:900.0 打八折时,您需要付:1200.0 打对折时,您需要付:750.0
策略模式将变化的方法(算法)通过一个接口进行抽象,然后在使用者中定义了这个接口,并在某个方法(算法)中调用了这个接口的方法(算法),处理或者返回结果。完成了算法在使用者内部解耦的作用。而使用者要更换方法(算法)的时候,可以通过setter方法进行灵活的更换。
策略模式就像我们游戏中的技能,就拿Dota来说,一个角色一般拥有四个技能,当我们技能能够使用的时候(级别满了,当然,CD时间也要有),我们可以灵活的使用技能,技能之间能互换,只是你在按某个技能键的时候,系统自动帮你调用了对应的招式而已。如果你是矮人狙击手,那么,当你发送散弹的时候,就是在调用你具体的技能在地图的某个区域对附近的人进行减血。技能的使用就类似角色使用策略模式来完成操作的。
以上是我个人对策略模式的理解以及总结,拿出来只是为了共同勉励。如果有错误的地方欢迎指正。
参考资料:
Head first 设计模式 (美)弗里曼(Freeman,E.)
大话设计模式 程杰