前言
代码例子是来大话设计模式,本文主要是根据个人的理解,对书中的内容做学习笔记。如果个人理解的有问题,请各位大佬指正🙏。
基础遗忘了可以复习一下:
面向对象Java基础
简单了解UML类图
1、业务背景
商场收银软件
2、UML与代码
2.1 简单工厂实现
先复习,并看下怎么使用简单工厂实现
// 收费抽象类
public abstract class CashSuper {
// 收取费用的抽象方法,参数为单价和数量
public abstract double acceptCash(double price, int num);
}
// 正常收费
public class CashNormal extends CashSuper {
// 原价返回
public double acceptCash(double price, int num){
return price * num;
}
}
// 打折收费
public class CashRebate extends CashSuper {
// 定义折扣变量
private double moneyRebate = 1d;
// 初始化时必须输入折扣率。八折就输入0.8
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
// 计算收费时需要在原价基础上乘以折扣率
public double acceptCash(double price, int num){
return price * num * this.moneyRebate;
}
}
// 返利收费
public class CashReturn extends CashSuper {
private double moneyCondition = 0d; // 返利条件
private double moneyReturn = 0d; // 返利值
// 初始化时必须输入返利条件和返利值
// 如:“满300返100”,就是moneyCondition=300,moneyReturn=100
public CashReturn(double moneyRebate){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
// 计算收费时,当达到返利条件,就原价减去返利值
public double acceptCash(double price, int num){
double result = price * num;
if (moneyCondition>0 && result >= moneyCondition)
result = result - Math.floor(result / moneyCondition) * moneyReturn;
return result;
}
}
扩展:
Math.ceil()的理解 为向上取整(比其本身大的数取值) 取整的类型为double类型的整数 其小数部分变为0。
Math.floor()的理解 为向下取整(比其本身小的数取值) 取整的类型也是为double类型的整数其小数部分同样变为0.
// 收费工厂(收费对象生成工厂)
public class CashFactory {
public static CashSuper createCashAccept(int cashType){
CashSuper cs = null;
switch (cashType) {
case 1:
oper = new CashNormal();
break;
case 2:
oper = new CashRebate(0.8d);
break;
case 3:
oper = new CashRebate(0.7d);
break;
case 4:
oper = new CashReturn(300d,100d);
break;
}
return cs;
}
}
客户端程序主要部分:
double price = 0d; // 商品单价
int num = 0; // 商品购买数量
double totalPrice = 0d; // 当前商品合计费用
double total = 0d; // 总计所有商品费用
...
// 简单工厂模式根据discount的数字选择合适的收费类 生成实例
CashSuper csuper = CashFactory.createCashAccept(discount);
// 通过多态,可以根据不同的收费策略计算的到收费结果
totalPrice = csuper.acceptCash(price,num);
total = total + totalPrice;
- 问题:
加入新的促销手段,是可以使用简单工厂模式来解决,但它只解决对象创建的问题,而且由于工厂本身包扩所有的收费方式,如果是经常性的变动,每次维护或扩展都需要改动工厂,导致代码得重新编译部署,这该怎么办?
2.2 策略模式
策略模式:它定义了算法家族,分别封装,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的用户。
对于如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能相互替换的,这就是变化点,而封装变化点是我们面向对象的重要思维。
Strategy类,定义所有支持的算法的公共接口:
public abstract class Strategy {
public abstract void algorithmInterface();
}
ConcreteStrategy类,封装了具体的算法或行为,继承Strategy:
// A B C都一样
public class ConcreteStrategyA extends Strategy{
public void algorithmInterface(){
System.out.println("算法A实现");
}
}
✨Context类,用一个ConcreteStrategy对象来配置,维护一个对Strategy对象的引用。【策略模式的核心】
public class Context {
Strategy strategy;
// 初始化时,传入具体的策略对象【这里就是传入ConcreteStrategy对象,列入ConcreteStrategyA】
public Context(Strategy strategy){
this.strategy = strategy;
}
// 上下文接口
public void contextInterface(){
// 根据具体的策略对象,调用其算法的方法
strategy.algorithmInterface();
}
}
对UML在代码中聚合的个人理解:
客户端代码
Context context;
// 由于实例化不同的策略,所以最终在调用context.contextInterface()时,
// 获得的结果就不尽相同
context = new Context(new ConcreteStrategyA()); // 子类 - 具体算法(策略)
context.contextInterface();
2.3 简单工厂模式 和 策略模式 的区别✨✨
- 简单工厂模式:用Factory工厂类创建对象(策略,具体算法)
- 策略模式:用Context管理对象(策略,具体算法),不负责创建对象
策略模式最主要的作用:相比Factory,Context不会因为同一个接口contextInterface()
添加新的策略(算法,例如ConcreteStrategyD()
)而需要改动Context原本的代码。
【这里就根据上述例子理解,管理是Context获取抽象类(Strategy,对应客户端代码的new Context(new ConcreteStrategyA())
),然后调用方法context.contextInterface()
操作对象的方法】
2.4 策略模式实现✨
将商场收银软件使用策略模式实现,先看UML的变化。
由上图,我们可以知道,抽象类部分没有改变,这里就不做代码展示,遗忘的可以看2.1节的代码,这里就展示 context类 和 客户端代码 具体怎么写。
Context类
public class CashContext {
// 声明Strategy类对象 【UML中的抽象类 收费】
private CashSuper cs;
// 通过构造方法,传入具体的收费策略【Strategy】
public Context(CashSuper csuper){
// 传入的策略csuper交给CashContext用声明的cs具体管理
this.cs = csuper;
}
// 如果抽象方法有多个,这种管理方法也可以写多个【注意:每个子类都必须实现】
// 管理cs对象统一的抽象方法【cs是图指的 被管理对象】
public double getResult(double price,int num){
// 根据传入不同的收费策略,获得具体实现的返回结果
return this.cs.acceptCash(price,num);
}
}
客户端主要代码
CashContext cc = null;
// 根据用户输入,将对应的策略对象作为参数参入CashContext对象中
switch(discount){
case 1:
cc = new CashContext(new CashNormal());
break;
case 2:
cc = new CashContext(new CashRebate(0.8d));
break;
case 3:
...
}
// 通过Context的getResult方法的调用,可以得到收取费用的结果
// 让具体算法与客户进行隔离【即使是工厂模式也一样】
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
- 问题:
这个样子写,就需要在客户端去判断用哪一个算法。
【客户端代码相当于界面逻辑部分,应该与业务逻辑分开,这里客户端代码中创建各种策略,算是业务逻辑和界面逻辑耦合在一起(个人理解)】
2.5 策略与简单工厂结合✨
为了让业务逻辑和界面逻辑解耦,这里让策略模式与简单工厂模式结合,将判断的代码转移。
public class CashContext {
// 声明Strategy类对象 【UML中的抽象类 收费】
private CashSuper cs;
// 通过构造方法,传入具体的收费策略【Strategy】
// public Context(CashSuper csuper){ 【原本的写法】
public Context(int cashType){
// 根据类型,new一个策略交给CashContext用声明的cs具体管理
switch(cashType){
case 1:
this.cs = new CashContext(new CashNormal());
break;
case 2:
this.cs = new CashContext(new CashRebate(0.8d));
break;
case 3:
...
}
// 如果抽象方法有多个,这种管理方法也可以写多个【注意:每个子类都必须实现】
// 管理cs对象统一的抽象方法【cs是图指的 被管理对象】
public double getResult(double price,int num){
// 根据传入不同的收费策略,获得具体实现的返回结果
return this.cs.acceptCash(price,num);
}
}
改动的个人理解:
客户端代码:
CashContext cc = new CashContext(discount);
// 通过Context的getResult方法的调用,可以得到收取费用的结果
// 让具体算法与客户进行隔离【即使是工厂模式也一样】
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
根据上述内容可以发现,简单工厂模式并非只有建一个工厂类的做法
- 疑惑1:
难道这个策略与工厂结合后,还是工厂模式?【小白的我就先不纠结了】
下面对比一下 简单工厂模式 与 策略模式与简单工厂结合
// 简单工厂模式
CashSuper csuper = CashFactory.createCashAccept(dscount);
totalPrices = csuper.acceptCash(price,num);
// 策略模式与简单工厂结合
CashContext cc = new CashContext(discount);
totalPrice = cc.getResult(price,num)
- 疑惑2:
如果将客户端的判断代码转移到context里面,那context不需要改动的优势不就没有了吗
【但在另一方面(如上图),又进行了另一方面的解耦】