
一.需求
小王在家附近开了一家便利店。进货、上架……经过一番折腾,总算可以开始卖货了。
等等!至少还需要一个收银台吧?好在小王是程序员出身,这点小事难不倒他。很快,小王就开发了一套收银软件,可以正常进行收银了。附近的居民都来店里买东西,生意红红火火,小王的心里美滋滋的。
好景不长,附近又新开了一家更大的便利店,不少顾客都跑去那边买东西了,而且人家还有促销活动。
小王终于坐不住了,琢磨着自己的店也得搞点促销活动才行。他准备搞一次满100减10元的活动。这样一来,之前的软件就需要进行升级了,这点需求同样难不倒小王,说干就干。
二.初步尝试
之前的逻辑:
class Goods { // 商品
private String name; // 商品名称
private double number; // 数量
private double price; // 单价
public Goods(String name, double number, double price) {
this.name = name;
this.number = number;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getNumber() {
return number;
}
public void setNumber(double number) {
this.number = number;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
class Charge { // 计费模块
public double charge(List<Goods> goodsList) {
double sum = 0;
for (Goods goods : goodsList) {
sum += goods.getPrice() * goods.getNumber();
}
return sum;
}
}
class CheckStand { // 收银台
public static void main(String[] args) {
List<Goods> goodsList = new ArrayList<Goods>();
goodsList.add(new Goods("可乐", 20, 3.5));
goodsList.add(new Goods("牛奶", 30, 2.5));
Charge charge = new Charge();
printShoppingList(goodsList, charge.charge(goodsList));
}
// 打印购物清单
private static void printShoppingList(List<Goods> goodsList, double sum) {
System.out.println("您本次消费:" + sum + "元,购物清单如下:");
for (Goods goods : goodsList) {
System.out.println(goods.getName() + "\t" + goods.getNumber() + "*" + goods.getPrice());
}
}
}
显然,需要修改计费模块,将计费的公式进行修改。很快,小王就改好了:
public double charge(List<Goods> goodsList) {
double sum = 0;
for (Goods goods : goodsList) {
sum += goods.getPrice() * goods.getNumber();
}
// 以下是新增的逻辑
if (sum >= 100) {
sum = sum - 10;
}
return sum;
}
运行一下看看:
您本次消费:135.0元,购物清单如下:
可乐 20.0*3.5
牛奶 30.0*2.5
看起来目前一切都很顺利,来买东西的人络绎不绝,小王的生意又重新红火了起来。
但是,又出现了一个新的问题:店里的牛奶很多快要过期了,如果再不处理,恐怕亏损巨大!!!怎么办?小王决定甩卖,买一袋送一袋,如果参与买一送一活动,就不再参与之前的满减活动了。之前的程序还要再改:
public double charge(List<Goods> goodsList) {
double sum = 0;
boolean twoForOne = false;
for (Goods goods : goodsList) {
if (goods.getName().equals("牛奶")) { // 牛奶买一送一
int num = (int)goods.getNumber() / 2; // 求出数量的一半
if ((int)goods.getNumber() % 2 != 0) { // 如果是奇数个,还需要再加1
num += 1;
}
sum += goods.getPrice() * num;
twoForOne = true;
} else {
sum += goods.getPrice() * goods.getNumber();
}
}
if (twoForOne == false) { // 没有参与牛奶买一送一,才可以参与满减
if (sum >= 100) {
sum = sum - 10;
}
}
return sum;
}
再运行一次看看:
您本次消费:107.5元,购物清单如下:
可乐 20.0*3.5
牛奶 30.0*2.5
虽然可以运行,但是charge代码看起来越来越繁琐,如果过一段时间可乐也参与满一送一呢?如果想满200减30呢?每次都要修改charge的代码。
有没有更好的方案呢?小王陷入了深深的思索。
三.更好的方案
以上实现有两个主要问题:
1.经常需要修改的是进行计费的charge方法,一旦有新的优惠活动,就需要修改这里。我们都知道,程序应该对扩展开放,对修改关闭,显然,这里并不满足。
2.优惠活动可能会有很多,但活动种类其实是非常有限的,我们能不能抽象出常见的优惠活动呢?
显然,以上两个问题的答案都是:Yes!
1.我们可以实现不同类型的Charge类,针对不同的优惠活动只需使用不同的Charge类即可,当有新的优惠活动时,我们只需实现新的Charge类,老的无需修改。
2.我们可以开发出几种不同的优惠活动类,例如满m减n,买m送n。
来看我们的新代码:
/**
* 定义营销活动
*/
enum Promotion {
MONEY_OFF, // 满减
PRESENTATION; // 买就送
}
class Goods {
private String name; // 商品名称
private double number; // 数量
private double price; // 单价
private Promotion promotion; // 参与哪种促销活动
public Goods(String name, double number, double price, Promotion promotion) {
this.name = name;
this.number = number;
this.price = price;
this.promotion = promotion;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getNumber() {
return number;
}
public void setNumber(double number) {
this.number = number;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Promotion getPromotion() {
return promotion;
}
public void setPromotion(Promotion promotion) {
this.promotion = promotion;
}
}
/**
* 抽象的计费类
*/
abstract class Charge {
abstract public double charge(List<Goods> goodsList);
}
/**
* 满减营销活动计费
*/
class MoneyOffCharge extends Charge {
private double threshold; // 满多少
private double off; // 减多少
public MoneyOffCharge(double threshold, double off) {
this.threshold = threshold;
this.off = off;
}
@Override
public double charge(List<Goods> goodsList) {
double sum = 0;
double promotionSum = 0;// 活动商品总价
for (Goods goods : goodsList) {
if (goods.getPromotion() == Promotion.MONEY_OFF) { // 如果该商品参与满减活动
promotionSum += goods.getNumber() * goods.getPrice();
} else { // 不参与活动
sum += goods.getNumber() * goods.getPrice();
}
}
sum += promotionSum;
if (promotionSum >= threshold) {
sum = sum - off;
}
return sum;
}
}
/**
* 买就送营销活动计费
*/
class PresentationCharge extends Charge {
private double number; // 买多少
private double presentation; // 送多少
public PresentationCharge(double number, double presentation) {
this.number = number;
this.presentation = presentation;
}
@Override
public double charge(List<Goods> goodsList) {
double sum = 0;
for (Goods goods : goodsList) {
if (goods.getPromotion() == Promotion.PRESENTATION) { // 如果该商品参与买就送活动
if (goods.getNumber() >= number) { // 达到活动条件
// number + presentation 为一组,计算有多少组
int chargeGroup = (int)(goods.getNumber() / (number + presentation));
// 计算分组后还剩余多少个
double rest = goods.getNumber() % (number + presentation);
// 计算在rest中有多少个需要付费,取rest跟number之中较小的
double chargeRest = rest > number ? number : rest;
double chargeNumber = chargeGroup * number + chargeRest;
sum += chargeNumber * goods.getPrice();
}
} else { // 不参与活动
sum += goods.getNumber() * goods.getPrice();
}
}
return sum;
}
}
class CheckStand {
public static void main(String[] args) {
List<Goods> goodsList = new ArrayList<Goods>();
goodsList.add(new Goods("可乐", 20, 3.5, Promotion.MONEY_OFF));
goodsList.add(new Goods("牛奶", 30, 2.5, Promotion.MONEY_OFF));
Charge charge = new MoneyOffCharge(100, 10);
printShoppingList(goodsList, charge.charge(goodsList));
}
private static void printShoppingList(List<Goods> goodsList, double sum) {
System.out.println("您本次消费:" + sum + "元,购物清单如下:");
for (Goods goods : goodsList) {
System.out.println(goods.getName() + "\t" + goods.getNumber() + "*" + goods.getPrice());
}
}
}
运行一下:
您本次消费:135.0元,购物清单如下:
可乐 20.0*3.5
牛奶 30.0*2.5
这一次,小王的脸上终于露出了久违的笑容,再也不用担心因为促销活动要反复修改代码了。
四.模式总结
在上面的新方案中,我们其实已经使用了策略模式。
类图

Context类:上下文,可以是不同的业务场景,在这个场景中会有一些处理步骤设计到多种处理方式。
Strategy接口:定义算法接口。
ConcreteStrategyA类:具体的算法实现类。
在我们上面的代码中,收银台CheckStand 就是Context类,Charge类就是Strategy接口(注意,接口并不意味着一定要使用interface,而是范围抽象的父类或接口),MoneyOffCharge和PresentationCharge就是具体的算法实现类。
使用场景
某个行为有多种算法实现,运行时需要灵活在多种算法间切换。
优点
1.可以在运行时动态改变行为方式,增加灵活性
2.将算法变化与客户端独立,增加维护性与可扩展性
缺点
客户端必须知道所有的策略类,并自行决定使用哪一个
参考资料:
《Head First设计模式》
本文已迁移至我的博客:http://ipenge.com/6863.html
网友评论