1、模板模式
父类定义算法骨架,子类实现算法细节
定义骨架:
骨架的意思就是一整个流程的描述,像网上购物和现实购物,它们都会有选择商品、支付、开发票、派送商品这些流程,只是它们对于这些流程的实现方法不一样,一个是线上支付,一个是线下支付等等,这样我们就可以设计一个公共的接口。
public abstract class OrderProcessTemplate {
/**
* 处理订单: 定义好算法骨架
*/
public final void processOrder(){
//1、选择商品
doSelect();
//2、进行支付
doPayment();
//3、开具发票
doReceipt();
//4、派送商品
doDelivery();
}
public abstract void doSelect();
public abstract void doPayment();
public abstract void doReceipt();
public abstract void doDelivery();
}
定义网上购物
package com.atguigu.designpattern.template.impl;
import com.atguigu.designpattern.template.OrderProcessTemplate;
/**
* @author lfy
* @Description 网络订单:算法细节实现
* @create 2022-12-29 20:24
*/
public class NetOrder extends OrderProcessTemplate {
@Override
public void doSelect() {
System.out.println("把 xiaomi11 加入购物车");
}
@Override
public void doPayment() {
System.out.println("在线微信支付 1999");
}
@Override
public void doReceipt() {
System.out.println("发票已经发送给用户邮箱: [email protected]");
}
@Override
public void doDelivery() {
System.out.println("顺丰次日达:投送商品");
}
}
定义现实购物
package com.atguigu.designpattern.template.impl;
import com.atguigu.designpattern.template.OrderProcessTemplate;
/**
* @author lfy
* @Description 门店订单:子类实现具体算法
* @create 2022-12-29 20:26
*/
public class StoreOrder extends OrderProcessTemplate {
@Override
public void doSelect() {
System.out.println("用户选择了:3号货架-xiaomi11 商品");
}
@Override
public void doPayment() {
System.out.println("刷卡机:刷卡支付 1999");
}
@Override
public void doReceipt() {
System.out.println("打印发票,和物品一起包装");
}
@Override
public void doDelivery() {
System.out.println("把商品交给用户,用漂亮的袋子");
}
}
入口类:
public class TemplateMethodPatternTest {
public static void main(String[] args) {
//行为型模式玩的就是一个多态
//1、外界调用模板类【遵循依赖反转原则】【依赖抽象而不是细节】
OrderProcessTemplate processTemplate = new NetOrder();
System.out.println("网络订单:");
//处理订单
processTemplate.processOrder(); //定义了算法的模板
processTemplate = new StoreOrder();
System.out.println("门店订单:");
processTemplate.processOrder();
}
}
总结:
模板设计模式就是定义出一个大的模板,用来描述一整个流程,根据这个流程有多种不同的实现,比如说设计一个用户从下单支付到生成订单发货的一整个流程就可以设计成一个模板,然后这一整个流程可以在线下也可以在线上,这取决于你要使用哪种模板
2、策略模式
基于具体的业务的不同的实现,比如当前业务是要实现一个排序算法,而排序算法有很多种,这样我们就可以定义一个接口,这样不同的算法我们只需要实现这个接口即可,而这些不同的算法也叫不同的策略
定义接口:
public interface SortStrategy{
void sort(Integer[] arr);
}
定义策略:
冒泡排序策略:
public class BubbleSortStrategy implements SortStrategy{
@Override
public void sort(Integer[] arr){
System.out.println("开始冒泡排序...");
for(int i=0;i<arr.length-1;i++){
for(int j=0; j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
Integer temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("排序结果"+Arrays.asList(arr));
}
}
快速排序策略:
public class QuickSortStrategy implements SortStrategy{
@Override
public void sort(Integer[] arr){
System.out.println("开始快速排序...");
Arrays.sort(arr);
System.out.println("排序结果"+arr);
}
}
入口类:
public class SortContext{
private SortStrategy strategy;
//构造方法,强行传入一个SortStrategy的实现
public SortContext (SortStrategy strategy){
this.strategy=strategy;
}
//设置排序的实现,用于更新策略
public void setStrategy(SortStrategy strategy){
this.strategy=strategy;
}
//具体排序的实现
public void sort(Integer[] arr){
strategy.sort(arr);
}
}
总结:
策略模式就是根据具体的业务实现不同的策略,比如说设计一个用户下单支付的功能,而下单支付又分为很多种支付方式,如微信支付、支付宝支付,这些他都是属于一个支付策略。
3、策略模式和模板模式
模板模式定义框架,策略模式定义细节
Service类:
@Service
@Slf4j //模板类
public class PayServiceImpl implements PayService {
@Autowired
List<PayStrategy> payStrategies; //注入支付策略
/**
* 生成收银台页面
* @param type
* @param orderId
* @return
*/
@Override
public String payPage(String type, Long orderId) {
//1、查询数据库订单
OrderInfo orderInfo = getOrderInfo(orderId);
//2、生成支付页
for (PayStrategy strategy : payStrategies) {
if(strategy.supports(type)){
//获取收银台页面
return strategy.cashierPage(orderInfo);
}
}
//3、如果以上都不支持,打印错误
return "不支持这种支付方式";
}
/**
* 定义通知处理模板;
* 微信通知
* 支付宝通知
* 1)、验证签名
* 2)、验证通过改订单为已支付
* 3)、验证通过给支付宝(success)微信(200状态码json)返回数据
* 4)、xxx
* @param request
* @param body
* @return
*/
@Override
public Object processNotify(HttpServletRequest request,String body) {
Object result = "不支持此方式";
//1、判断是那种通知
String type = getNotifyType(request);
Map<String, Object> data = null;
//2、验证签名
for (PayStrategy strategy : payStrategies) {
if(strategy.supports(type)){
//签名校验
boolean checkSign = strategy.checkSign(request,body);
if(!checkSign){
log.error("签名验证失败,疑似攻击请求");
//验签失败返回
return strategy.signError();
}else {
log.info("签名验证成功,提取通知数据");
//验签成功处理数据
data = strategy.process(request,body);
//验签成功返回
result = strategy.signOk();
}
}
}
//3、通用的后续处理算法;处理订单数据
processOrder(data);
return result;
}
/**
* 处理订单数据
* @param data
*/
private void processOrder(Map<String, Object> data) {
//TODO 把支付成功信息等保存数据库,并修改订单状态,通知库存系统等...
log.info("订单支付成功,状态修改完成,已通知库存系统,详细数据:{}",data);
}
/**
* 判断通知类型
* @param request
* @return
*/
private String getNotifyType(HttpServletRequest request) {
String header = request.getHeader("wechatpay-serial");
if(StringUtils.hasText(header)){
return "weixin";
}
String app_id = request.getParameter("app_id");
if(StringUtils.hasText(app_id)){
return "alipay";
}
return "unknown";
}
public OrderInfo getOrderInfo(Long orderId){
log.info("查询数据库订单:{}",orderId);
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(orderId);
orderInfo.setTitle("尚品汇-商城-订单");
orderInfo.setComment("快点发货");
orderInfo.setDesc("买了一堆商品");
orderInfo.setPrice(new BigDecimal("9098.00"));
orderInfo.setExpireTime(new Date(System.currentTimeMillis()+30*60*1000));
return orderInfo;
}
}
这个Service类就是一个模板模式,它对用户从下单到付款到生成订单的一系列操作生成了一个模板
而其中的payPage方法生成收银台页面这个具体功能使用了策略模式:
首先看看支付策略的接口:
package com.atguigu.pay.strategy;
import com.atguigu.pay.model.OrderInfo;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @author lfy
* @Description 支付策略
* @create 2022-12-28 22:39
*/
public interface PayStrategy {
/**
* 支持哪种支付
* @param type
* @return
*/
boolean supports(String type);
/**
* 为某个订单展示收银台页面
* @return
*/
String cashierPage(OrderInfo orderInfo);
/**
* 验证签名
* @param request 原生请求
* @param body 请求体数据【请求体只能读取一次,所以controller拿到以后都往下传递即可】
* @return
*/
boolean checkSign(HttpServletRequest request,String body);
/**
* 验签错误处理
* @return
*/
Object signError();
/**
* 验签通过返回
* @return
*/
Object signOk();
/**
* 验签成功后处理通知数据: 把通知的所有数据封装指定对象
* @param request
* @return
*/
Map<String,Object> process(HttpServletRequest request,String body);
}
再看看具体的实现:
支付宝支付策略:
package com.atguigu.pay.strategy.impl;
/**
* @author lfy
* @Description 支付宝
* @create 2022-12-28 22:40
*/
@Slf4j
@Component
public class AlipayStrategy implements PayStrategy {
@Autowired
AlipayProperties alipayProperties;
@Autowired
AlipayClient alipayClient;
@Override
public String cashierPage(OrderInfo orderInfo) {
//1、创建一个 AlipayClient
//2、创建一个支付请求
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
//3、设置参数
alipayRequest.setReturnUrl(alipayProperties.getReturn_url()); //同步回调:支付成功以后,浏览器要跳转到的页面地址
alipayRequest.setNotifyUrl(alipayProperties.getNotify_url()); //通知回调:支付成功以后,支付消息会通知给这个地址
//商户订单号(对外交易号)
String outTradeNo = orderInfo.getId().toString();
//付款金额
BigDecimal totalAmount = orderInfo.getPrice();
//订单名称
String orderName = "尚品汇-订单-"+outTradeNo;
//商品描述
String tradeBody = orderInfo.getDesc();
//详细:https://opendocs.alipay.com/open/028r8t?scene=22
//业务参数
Map<String,Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no",outTradeNo);
bizContent.put("total_amount",totalAmount);
bizContent.put("subject",orderName);
bizContent.put("body",tradeBody);
bizContent.put("product_code","FAST_INSTANT_TRADE_PAY");
//自动关单
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(orderInfo.getExpireTime());
bizContent.put("time_expire",date);
alipayRequest.setBizContent(JSON.toJSONString(bizContent));
//生成支付页面
String page = null;
try {
page = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
return page;
}
@Override
public boolean checkSign(HttpServletRequest request,String body) {
Map<String, String> params = HttpUtils.getParameterMap(request);
log.info("支付宝通知验证签名...");
//验证签名
try {
//调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params,
alipayProperties.getAlipay_public_key(), alipayProperties.getCharset(),
alipayProperties.getSign_type());
return signVerified;
} catch (AlipayApiException e) {
e.printStackTrace();
}
return false;
}
@Override
public Object signError() {
return "error";
}
@Override
public Map<String,Object> process(HttpServletRequest request,String body) {
Map<String, String> map = HttpUtils.getParameterMap(request);
String json = JSON.toJSONString(map);
Map<String, Object> data = JSON.parseObject(json, new TypeReference<Map<String, Object>>() {
});
return data;
}
@Override
public Object signOk() {
//支付宝要求成功返回 success 字符串
return "success";
}
@Override //对新增开放,对修改关闭
public boolean supports(String type) {
return "alipay".equalsIgnoreCase(type);
}
}
像微信支付接口之类的与支付宝相同,都是实现了PayStrategy这个接口的方法,并把它们通过@Component注解注入到容器中,此时如果需要新增功能如银联支付等等,只需要跟它们一样实现PayStrategy接口并注入到容器中即可。
这个时候再来看Service的生成付款页面的代码
@Service
@Slf4j //模板类
public class PayServiceImpl implements PayService {
@Autowired
List<PayStrategy> payStrategies; //注入支付策略
/**
* 生成收银台页面
* @param type
* @param orderId
* @return
*/
@Override
public String payPage(String type, Long orderId) {
//1、查询数据库订单
OrderInfo orderInfo = getOrderInfo(orderId);
//2、生成支付页
for (PayStrategy strategy : payStrategies) {
if(strategy.supports(type)){
//获取收银台页面
return strategy.cashierPage(orderInfo);
}
}
//3、如果以上都不支持,打印错误
return "不支持这种支付方式";
}
它先通过@Autowired将所有的支付策略如支付宝支付、微信支付等全部注入到一个List<PayStrategy>的集合中,因为它们的共性都是实现了PayStrategy,
然后使用一个for循环,通过前端传过来的type,来判断是哪个接口
这是前端传过来的参数:
点击微信支付按钮传过来的type就是weixin,点击支付宝支付传过来的就是alipay
然后调用了每个策略的supports方法,将type传入
以支付宝支付的supports方法为例:
@Override
public boolean supports(String type) {
return "alipay".equalsIgnoreCase(type);
}
如果返回的是true则证明用户点击的是支付宝支付,则使用支付宝支付的策略,否则继续遍历。
如果返回为true则会调用该策略的cashierPage(orderInfo);方法去生成一个支付页面返回给前端。
然后代码中设计了一个监听,来监听用户是否已经成功支付,与设计模式无关此处不展示。
此后由于每个支付策略支付成功后的返回签名数据都不一致,比如支付宝返回的是一个json字符串,微信返回的是一个状态码等等...
如果服务端收到了支付成功的请求则调用service的processNotify方法
@Override
public Object processNotify(HttpServletRequest request,String body) {
Object result = "不支持此方式";
//1、判断是那种通知
String type = getNotifyType(request);
Map<String, Object> data = null;
//2、验证签名
for (PayStrategy strategy : payStrategies) {
if(strategy.supports(type)){
//签名校验
boolean checkSign = strategy.checkSign(request,body);
if(!checkSign){
log.error("签名验证失败,疑似攻击请求");
//验签失败返回
return strategy.signError();
}else {
log.info("签名验证成功,提取通知数据");
//验签成功处理数据
data = strategy.process(request,body);
//验签成功返回
result = strategy.signOk();
}
}
}
//3、通用的后续处理算法;处理订单数据
processOrder(data);
return result;
}
此方法依然使用了策略模式,用注入的所有策略模式的List集合来进行遍历判断,如果判断成立则会调用该策略的验证签名方法,否则就会继续循环遍历,如果type成立而签名不成立,则被视为攻击请求非正常支付成功。
总结:
策略模式,只需要生成出具体的策略实现类,如支付宝支付策略,微信支付策略用来实现这个PayService即可,后续如果需要增加新的支付策略,只需要实现这个接口,不用在原本的代码上进行改动。模板策略模式用来定义流程,策略模式用来实现流程中的步骤的策略,二者相似但不一致,可以一起使用并不冲突。总的来说模板模式大,策略模式小!其实策略模式和模板模式大家听起来觉得一样这很正常,不管是什么设计模式,它都有一定的异曲同工之妙,使用哪种设计模式不是必须的,这取决于你业务的大小,比如上面这个例子,以支付流程为模板,具体支付为策略,你也可以选择以支付流程为策略,这取决于你自己,并不是说你这么做就不对,而是取决于你对业务粒度的划分。