欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式--策略模式

程序员文章站 2024-02-07 16:46:34
...

 


策略模式

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

 类图


设计模式--策略模式

 

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

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

这里引出了几个设计原则:
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.)

大话设计模式  程杰