设计模式--策略模式


策略模式

        定义:定义了算法族,分别将他们封装起来,让它们之间能相互替换。此模式让算法独立于使用算法的用户。

 类图


 

下面我们用两个列子来说明策略模式的实际用途。

1. 从上班开始

有一个程序员类,他有一个名称,然后有一个上班的方法:



 代码如下:

扫描二维码关注公众号,回复: 685346 查看本文章

 

 

//程序员类
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方法,就行了,不需要改动程序员类,程序员类也不用知道有没有骑自行车上班这个方法。充分解耦了程序员类与上班方式之间的关系。

这里引出了几个设计原则:
1. 解耦:我个人认为就是将变化的部分分离出来,想想,如果我们把这几种上班方式都放在一个类里会怎么样:


 

2. 针对接口编程:我们只需要针对上班方式这个接口编程,不需要去修改程序员类。

再举第二个例子:商场收银系统,收银除了计算商品单价、数量和总价外,有时候要做活动,比如:打八折、打五折,满500送减200等等。我们就拿单个商品来说:(因为真正的商场收费系统还比较复杂,因为要计算每一种商品的价格,数量,折扣,而且还要计算总价的折扣方式,商品和商场都有各自的折扣方式)


//结账类
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.)

大话设计模式  程杰

 

 

 

 

 

猜你喜欢

转载自chwshuang.iteye.com/blog/1607234