设计模式 - 策略模式
前言:
先不管模式, 把他和他的名字都忘了, 来看看问题 和 设计思路. 为啥要这么做.
场景:
有一家店铺, 里面有一个售货员, 售货员当然是要卖东西的啦, 客户进来买完东西, 找售货员结账, 那售货员得知道一共多少钱吧?
一. 初步设计
商品类:
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.
通过上面的例子, 可以看出, 具体的算法与算法之间没有依赖关系, 都是平等的(平等性), 可以相互替换的. 那在运行的时候, 每次都只能使用一种(唯一性).