持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
上一篇文章介绍了什么是面向对象编程,以及简单的工厂模式,第二篇文章呢说的是23种设计模式中的策略模式,对,就是本专栏第一个设计模式文章!首先设计模式这四个字我想应该很容易理解,说白点就是做一件事的不同方法,最终效果是一样的,但是做的过程是不同的。
tips:在本人知乎专栏中有一篇Thread类源码解析的文章,Thread类实际上用到的就是策略模式。这两篇文章都看,相信你会掌握策略模式的!
用户故事:我想要一个商场收银软件,根据客户所购买商品的单价和数量计算出客户需要支付的费用
故事分析及设计
我们大概需要做这样一些事:
- 知道用户买了哪些商品,以及商品单价与购买数量(单个商品的费用为单价乘以数量)
- 通过运算得到最终需要支付的金额
功能实现
好吧,这次找不到合适人选,还是小林来实现这个功能。
小林:哎呦,你这不是打我脸吗?这功能不是很简单吗,思路清晰的不行,我秒写,看好喽!
五分钟,小林写下来这样的一段代码:
public class StoreCashier {
public static void main(String[] args) {
List<Shop> shopList = new ArrayList<>();
shopList.add(new Shop(1, 1.0, "豆沙包"));
shopList.add(new Shop(1, 2.5, "大肉包"));
shopList.add(new Shop(1, 2.0, "绿豆粥"));
shopList.add(new Shop(1, 2.5, "早餐豆浆"));
System.out.println("购买商品的总价格是:" + getCount(shopList));
}
public static double getCount(List<Shop> list) {
// 定义一个变量用于统计总价格
double allCount = 0.0d;
for (Shop shop : list) {
allCount += shop.getNum() * shop.getPrice();
}
return allCount;
}
}
有点东西啊小林,五分钟就解决了,你以后一定是个人才。小林如果要增加一个打八折活动可以实现吗?
小林稍加思索:很简单啊,我在总金额后面乘0.8不就行了?
那如果是有些商品打八折,有些商品打七折呢?你怎么改?
小林陷入了沉思,突然灵光一现:我有思路了,我可以添加一个参数商品类型,然后使用switch语句分情况进行,打八折的商品乘以0.8,打七折的商品乘以0.7。我把代码写出来了,你看看吧。
public class StoreCashier {
public static void main(String[] args) {
List<Shop> shopList = new ArrayList<>();
shopList.add(new Shop(1, 1.0, "豆沙包"));
shopList.add(new Shop(1, 2.5, "大肉包"));
shopList.add(new Shop(1, 2.0, "绿豆粥"));
shopList.add(new Shop(1, 2.5, "早餐豆浆"));
System.out.println("正常收费购买商品的总价格是:" + getCount(shopList, "正常收费"));
System.out.println("打八折购买商品的总价格是:" + getCount(shopList, "打八折"));
System.out.println("打七折购买商品的总价格是:" + getCount(shopList, "打七折"));
System.out.println("打五折购买商品的总价格是:" + getCount(shopList, "打五折"));
}
public static double getCount(List<Shop> list, String discountType) {
// 定义一个变量用于统计总价格
double allCount = 0.0d;
switch (discountType) {
case "正常收费":
for (Shop shop : list) {
allCount += shop.getNum() * shop.getPrice();
}
break;
case "打八折":
for (Shop shop : list) {
allCount += shop.getNum() * shop.getPrice() * 0.8;
}
break;
case "打七折":
for (Shop shop : list) {
allCount += shop.getNum() * shop.getPrice() * 0.7;
}
break;
case "打五折":
for (Shop shop : list) {
allCount += shop.getNum() * shop.getPrice() * 0.5;
}
break;
}
return allCount;
}
}
思路清晰,不错。但是存在一些问题,比如四个分支的for循序是不是一模一样的?这不是主要的,现在各平台满减活动是数不胜数,如果我想加一个满5减1的活动,应该怎么实现呢?如果我从一折达到九折,什么折都打,你要写多个case?
小林思考了许久:我想起来上篇文章的简单工厂模式,我可以抽象一个商品类出来,让普通商品,打折商品,满减商品分别去继承商品类,然后重写计算金额方法。这样代码就会好一点了。大概这这样:
// 商品基类
public class ShopCount {
public ShopCount(){
}
// 计算商品价格
public double getCount(double money){
return money;
}
}
// 正常商品直接返回money
public class ZhengchangShopCount extends ShopCount {
@Override
public double getCount(double money){
return money;
}
}
// 打折商品
public class DazheShopCount extends ShopCount{
// 存储打多少折
private double moneyRebate = 0d;
// 创建该商品需要一个参数,告诉我打多少折
public DazheShopCount(double moneyRebate){
this.moneyRebate = moneyRebate;
}
@Override
public double getCount(double money){
return money * moneyRebate;
}
}
// 满减商品:需要两个参数,一个是返利条件,一个是返利值
public class ManjianShopCount extends ShopCount {
// 返利条件
private double moneyCondition = 0d;
// 返利值
private double moneyReturn = 0;
public ManjianShopCount(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double getCount(double money){
if (money>moneyCondition){
return money - moneyReturn;
}else {
return money;
}
}
}
// 商品工厂
public class ShopCountFactory {
public static ShopCount createShopCount(String shopType){
ShopCount shopCount = null;
switch (shopType){
case "正常商品":
shopCount = new ZhengchangShopCount();
break;
case "打八折":
shopCount = new DazheShopCount(0.8);
break;
case "满5减1":
shopCount = new ManjianShopCount(5.0,1.0);
break;
}
return shopCount;
}
}
public class MainTest {
public static void main(String[] args) {
// 小林的日常早餐
List<Shop> shopList = new ArrayList<>();
shopList.add(new Shop(1, 2.5, "大肉包"));
shopList.add(new Shop(1, 2.0, "绿豆粥"));
shopList.add(new Shop(1, 1.5, "鸡蛋"));
// 统一小林应该付多少钱
double allCount = 0d;
for (Shop shop : shopList) {
allCount += shop.getNum() * shop.getPrice();
}
System.out.println("正常情况小林要支付:" + ShopCountFactory.createShopCount("正常商品").getCount(allCount));
System.out.println("打八折小林要支付:" + ShopCountFactory.createShopCount("打八折").getCount(allCount));
System.out.println("满5减1小林要支付:" + ShopCountFactory.createShopCount("满5减1").getCount(allCount));
}
}
不错,不错,小林你已经掌握了简单工厂模式了,恭喜你。既然你已经熟悉了简单工厂,那我们就来个其他的问题,虽然现在我们现在可以随意的增加打折的折扣力度,以及满减的规则。但是一年这么多个节日,超市中活动层出不穷,所以商品的类型(打折,原价,满减)变化是非常快的。我们在变得不是商品本身,因为它的名字之类的信息是不存在变化的,变化的计算价格的算法。 鉴于算法多变,我们不可能频繁修改工厂类,因为一旦修改就需要重新部署,机器多的话是很大的一个工作量。所以需要封装这个变化点。
小林想破脑袋也没有想到上面这段话是什么意思.
我们直接告诉他吧,首先看下策略模式比较正式的定义:
策略模式:它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
商场何时打折,何时满减,亦或是其他一些促销活动。变化的是计算总价的算法,我们用工厂去生成这些算法对象是可行的,但是这种算法变化是非常快的,我们称为变化点。在面向对象中,封装变化点是一种很重要的思维方式。
小林:我懂了,上个版本我写的ShopCount类实际上可以改为抽象策略接口,而原来的三个子类就可以实现抽象策略接口,这个抽象策略接口中就一个方法,那就是我们计算价格的策略!在这个基础上我加一个 ShopContext类去维护抽象策略接口的对象,通过多态来实现策略的变化。看看我写的代码吧!
// 抽象策略接口
public interface ShopCountSategy {
// 计算商品价格
double getCount(double money);
}
// 普通商品
public class ZhengchangShopCount implements ShopCountSategy {
@Override
public double getCount(double money){
return money;
}
}
// 满减商品
public class ManjianShopCount implements ShopCountSategy {
// 返利条件
private double moneyCondition = 0d;
// 返利值
private double moneyReturn = 0;
public ManjianShopCount(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double getCount(double money){
if (money>moneyCondition){
return money - moneyReturn;
}else {
return money;
}
}
}
// 打折商品
public class DazheShopCount implements ShopCountSategy {
private double moneyRebate = 0d;
public DazheShopCount(double moneyRebate){
this.moneyRebate = moneyRebate;
}
@Override
public double getCount(double money){
return money * moneyRebate;
}
}
public class ShopContext {
private ShopCountSategy shopCountSategy;
public ShopContext(ShopCountSategy shopCountSategy){
this.shopCountSategy = shopCountSategy;
}
public double startCount(double money) {
return shopCountSategy.getCount(money);
}
}
public class MainTest {
public static void main(String[] args) {
// 小林的日常早餐
List<Shop> shopList = new ArrayList<>();
shopList.add(new Shop(1, 2.5, "大肉包"));
shopList.add(new Shop(1, 2.0, "绿豆粥"));
shopList.add(new Shop(1, 1.5, "鸡蛋"));
// 统一小林应该付多少钱
double allCount = 0d;
for (Shop shop : shopList) {
allCount += shop.getNum() * shop.getPrice();
}
// 控制台判断商品类型(正常商品,打八折商品,满5减1商品)
String shopType = "打八折";
ShopContext shopContext = null;
switch (shopType) {
case "正常商品":
shopContext = new ShopContext(new ZhengchangShopCount());
break;
case "打八折":
shopContext = new ShopContext(new DazheShopCount(0.8));
break;
case "满5减1":
shopContext = new ShopContext(new ManjianShopCount(5.0, 1.0));
break;
}
System.out.println("小林应该支付:" + shopContext.startCount(allCount));
}
}
我们看看上面的代码是否可以在进一步呢?现在代码有的MainTest中有一个switch语句,switch的语句是我自己造的,也就是说现在是在客户端去判断用哪个算法。我们得想办法将这部分判断移出客户端。
小林:上篇文章讲的简单工厂,这篇文章是策略模式,哦,我突然有个大胆的想法,工厂不一定就是一个单独的类,我们可以将工厂模式与策略模式进行一个结合。这样还减少了耦合!哈哈,我改进一下:
public class ShopContext {
private ShopCountSategy shopCountSategy;
public ShopContext(String type){
switch (type){
case "正常商品":
shopCountSategy = new ZhengchangShopCount();
break;
case "打八折":
shopCountSategy = new DazheShopCount(0.8);
break;
case "满5减1":
shopCountSategy = new ManjianShopCount(5.0, 1.0);
break;
}
}
public double startCount(double money) {
return shopCountSategy.getCount(money);
}
}
public class MainTest {
public static void main(String[] args) {
// 小林的日常早餐
List<Shop> shopList = new ArrayList<>();
shopList.add(new Shop(1, 2.5, "大肉包"));
shopList.add(new Shop(1, 2.0, "绿豆粥"));
shopList.add(new Shop(1, 1.5, "鸡蛋"));
// 统一小林应该付多少钱
double allCount = 0d;
for (Shop shop : shopList) {
allCount += shop.getNum() * shop.getPrice();
}
// 控制台判断商品类型(正常商品,打八折商品,满5减1商品)
ShopContext zhengchangContext = new ShopContext("正常商品");
System.out.println("小林应该支付:" + zhengchangContext.startCount(allCount));
ShopContext dazheContext = new ShopContext("打八折");
System.out.println("小林应该支付:" + dazheContext.startCount(allCount));
ShopContext manjianContext = new ShopContext("满5减1");
System.out.println("小林应该支付:" + manjianContext.startCount(allCount));
}
}
小林:现在你看看,我的代码是不是就比较有设计感了呢?
小林,这我可得给你竖大拇指啊,孺子可教也!
自信源于努力,我是小林,我们下期见!