设计模式 - 策略模式

时间:2022-11-11 21:58:43
前言:

  先不管模式, 把他和他的名字都忘了, 来看看问题 和 设计思路. 为啥要这么做. 

场景:

  有一家店铺, 里面有一个售货员, 售货员当然是要卖东西的啦, 客户进来买完东西, 找售货员结账, 那售货员得知道一共多少钱吧?

一. 初步设计

  商品类:

package org.elvin.strategy;

/***/
public class Goods {
    /**
     * 商品名
     */
    private String name;

    /**
     * 商品价格
     */
    private Long Price;

    public Goods() { }

    public Goods(String name, Long price) {
        this.name = name;
        Price = price;
    }

    //region getter / setter
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getPrice() {
        return Price;
    }

    public void setPrice(Long price) {
        Price = price;
    }
    //endregion
}

 

由于价格我使用的是 Long 类型, 所以, 要有一个转换输出的方法.

package org.elvin.strategy;

import java.text.MessageFormat;

public class MoneyUtils {
    public static String getYuan(Long money){
        Long yuan = money / 100;
        Long jiao = money % 100 /  10;
        Long fen = money % 10;

        return MessageFormat.format("{0}.{1}{2}", yuan, jiao , fen );
    }
}

售货员:

package org.elvin.strategy;

import org.elvin.strategy.calculator.*;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * 环境角色(Context)*/
public class Seller {

    /**
     * 姓名
     */
    private String name;

    /**
     * 编号
     */
    private String code;

//region getter / setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; }
//endregion /** * 售卖商品 */ public void sellGoods(List<Goods> goods){ Long sum = 0L; for (Goods good : goods) { sum += good.getPrice(); } System.out.println("应付款: " + MoneyUtils.getYuan(sum) + " 元"); } @Test public void func1(){ List<Goods> goods = new ArrayList<>(); goods.add(new Goods("泡面", 550L)); goods.add(new Goods("泡面", 550L)); goods.add(new Goods("泡面", 550L)); goods.add(new Goods("火腿", 150L)); goods.add(new Goods("火腿", 150L)); goods.add(new Goods("火腿", 150L)); goods.add(new Goods("鸡蛋", 70L)); goods.add(new Goods("鸡蛋", 70L)); goods.add(new Goods("鸡蛋", 70L)); goods.add(new Goods("饼干", 250L)); goods.add(new Goods("辣条", 300L)); sellGoods(goods); } }

来看一下计算结果:

设计模式 - 策略模式

得到结果了, 没啥毛病, 挺好. 

今天老板过生日, 突然想到, 要不要再店里搞个活动, 来个优惠活动, 有些商品减价销售. 

明天老板娘过生日, 老板娘说, 老娘高兴, 你减价销售, 明天我来个免单销售.

那现在咋办呢? 明天过后, 价格肯定又要恢复到正常价格.  what the fuck! 

Seller类写死了, 难道我在里面加几个计算方法? 

加进去貌似可以解决当下问题, 但是, 后天如果老板的老丈人过生日呢? 咋搞?

 

Ok, 到这里, 差不多, 需要请出今天的大神 : 策略模式. 让他来帮我们解决这个问题吧.

 

二. 设计改造

对于售货员来说, 她必须知道, 今天该怎么计算价格, 是优惠还是不优惠, 或者是满多少钱, 送东西什么的. 

那么, 将这些优惠或者说价格的计算方法抽象出来, 成为一个接口或者抽象类. 

让优惠或者不优惠实现或者继承他.

实现方式:

这里, 我将他抽象为一个接口

package org.elvin.strategy.calculator;

import org.elvin.strategy.Goods;

import java.util.List;

/**
 * 抽象策略角色(Strategy)
 * 优惠接口
*/ public interface PreferentialPrice { public void getPrice(List<Goods> goods); }
PreferentialPrice 需要作为一个属性,出现在 Seller 类中.

在Seller中加入

    /**
     * 计算优惠后的价格
     * 抽象角色, 次角色给出所有具体策略类所需的接口
     */
    private PreferentialPrice preferentialPrice;

    public PreferentialPrice getPreferentialPrice() {
        return preferentialPrice;
    }

    public void setPreferentialPrice(PreferentialPrice preferentialPrice) {
        this.preferentialPrice = preferentialPrice;
    }

 

这里提供三种计算方式:

1. 正常方式

/**
 * 具体策略角色(ConcreteStrategy)
*/ public class NoPreferential implements PreferentialPrice { @Override public void getPrice(List<Goods> goods) { Long sum = 0L; for (Goods good : goods) { sum += good.getPrice(); } System.out.println("应付款: " + MoneyUtils.getYuan(sum) + " 元"); } }

2. 免单方式

/**
 * 具体策略角色(ConcreteStrategy)
*/ public class Free implements PreferentialPrice { @Override public void getPrice(List<Goods> goods) { System.out.println("免单, 不要钱 !"); } }

3. 部分优惠方式

/**
 * 具体策略角色(ConcreteStrategy)
*/ public class ReduceSomeGoods implements PreferentialPrice { @Override public void getPrice(List<Goods> goods) { Long sum = 0L; for (Goods good : goods) { switch (good.getName()) { case "泡面": sum += good.getPrice() - 50L; break; case "火腿": sum += good.getPrice() - 20L; break; case "鸡蛋": sum += good.getPrice() - 10L; break; default: sum += good.getPrice(); break; } } System.out.println("应付款: " + MoneyUtils.getYuan(sum) + " 元"); } }

  

 将Seller类中, 计算的方法修改一下:

    public void sellGoods(List<Goods> goods){
        if(preferentialPrice == null){
            setPreferentialPrice(new NoPreferential());
        }
        preferentialPrice.getPrice(goods);
    }

在计算的时候, 如果没有传入优惠, 则默认使用无优惠方式

再看测试方法:

    @Test
    public void func1(){
        List<Goods> goods = new ArrayList<>();
        goods.add(new Goods("泡面", 550L));
        goods.add(new Goods("泡面", 550L));
        goods.add(new Goods("泡面", 550L));
        goods.add(new Goods("火腿", 150L));
        goods.add(new Goods("火腿", 150L));
        goods.add(new Goods("火腿", 150L));
        goods.add(new Goods("鸡蛋", 70L));
        goods.add(new Goods("鸡蛋", 70L));
        goods.add(new Goods("鸡蛋", 70L));
        goods.add(new Goods("饼干", 250L));
        goods.add(new Goods("辣条", 300L));

        setPreferentialPrice(new Free());
        sellGoods(goods);
        System.out.println("-----------------------");
        setPreferentialPrice(new ReduceSomeGoods());
        sellGoods(goods);
        System.out.println("-----------------------");
        setPreferentialPrice(new NoPreferential());
        sellGoods(goods);
    }

结果:

  设计模式 - 策略模式

策略模式作为一种对象行为模式, 在这里应该还是体现到了吧.

那总结一下?给个不容易懂得(网上抄的):

  策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

说的比较抽象, 来个具体的吧:

  一帅哥喜欢约妹子, 那咋约出来呢? 不是所有的妹子都喜欢吃饭看电影吧. 那针对不同的妹子, 使用不同的方法来约. 约喜欢看电影的妹子看电影, 约喜欢吃小吃的妹子吃小吃.

那吃饭, 看电影, 吹海风...... 等等, 这些手段, 目的都是为了让妹子做他女朋友(这里不讨论时间). 目的不变, 手段层出不穷. 这些方法, 就可以理解为不同的 strategy. 

 

通过上面的例子, 可以看出, 具体的算法与算法之间没有依赖关系, 都是平等的(平等性), 可以相互替换的. 那在运行的时候, 每次都只能使用一种(唯一性).