目录

  • 1 代理模式
    • 1.1 文件服务应用
    • 1.2 分布式文件代理服务器实现
      • 1.2.1 实现分析
      • 1.2.2 代码实现
  • 2 享元模式
    • 2.1 用户信息共享实现
      • 2.1.1 会话跟踪分析
      • 2.1.2 代码实现
  • 3 装饰者模式
    • 3.1 结算价格嵌套运算
      • 3.1.1 订单价格结算分析
      • 3.1.2 价格结算实现
  • 4 策略模式
    • 4.1 根据VIP等级结算价格
      • 4.1.1 不用VIP优惠价格分析
      • 4.1.2 代码实现
  • 5 工厂模式
    • 5.1 支付收银
      • 5.1.1 支付渠道选中分析
      • 5.1.2 代码实现
  • 6 状态模式
    • 6.1 根据订单状态进行不同操作
      • 6.1.1 订单状态改变分析
      • 6.1.2 代码实现

1 代理模式

代理模式:给一个对象创建一个代理对象,通过代理对象可以使用该对象的功能。
案例:根据文件类型,将文件存储到不同服务。

1.1 文件服务应用

代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,如下图文件上传代理服务,针对一些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如商品视频介绍,我们可以把它存储到第三方OSS。
用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码层面充分运用了代理模式,在架构层面也充分运用了代理模式。

1.2 分布式文件代理服务器实现

1.2.1 实现分析

1、FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现。
2、AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件。
3、FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件。
4、FileUploadProxy是代理对象,供用户访问,调用了FileUpload的文件上传方法,为用户提供不同文件上传调用。
5、FileController是控制器,用于接收用户提交的文件,并调用代理FileUploadProxy实现文件上传。

1.2.2 代码实现

application.yml配置:

server:port: 18081
#指定服务处理指定的文件类型
upload:filemap:aliyunOSSFileUpload: mp4,avifastdfsFileUpload: png,jpg
#FastDFS配置
fastdfs:url: http://192.168.XXX.XXX:XXX/
#aliyun
aliyun:oss:endpoint: oss-cn-beijing.aliyuncs.comaccessKey: a7i6rXXXXXJdYX2accessKeySecret: MeSZPybPHXXXXXXXXXXXXEaUbfRtdH8gl4bucketName: sklllkey: video/backurl: https://sklll.XXXXXXXXXXX.aliyuncs.com/video/ #访问地址配置
spring:application:name: seckill-goodsdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456servlet:multipart:max-file-size: 100MB #上传文件大小配置

fastdfs客户端配置,fdfs_client.conf:

charset=UTF-8
tracker_server=192.168.XXX.XXX:XXX

FileUpload接口定义:

public interface FileUpload {/**** 文件上传* @param buffers:文件字节数组* @param extName:后缀名* @return*/String upload(byte[] buffers,String extName);
}

AliyunOSSFileUpload实现:

@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload{@Value("${aliyun.oss.endpoint}")private String endpoint;@Value("${aliyun.oss.accessKey}")private String accessKey;@Value("${aliyun.oss.accessKeySecret}")private String accessKeySecret;@Value("${aliyun.oss.key}")private String key;@Value("${aliyun.oss.bucketName}")private String bucketName;@Value("${aliyun.oss.backurl}")private String backurl;/***** 文件上传*  文件类型如果是图片,则上传到本地FastDFS*  文件类型如果是视频,则上传到aliyun OSS*/@Overridepublic String upload(byte[] buffers,String extName) {String realName = UUID.randomUUID().toString()+"."+extName ;// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret);// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key+realName, new ByteArrayInputStream(buffers));// 上传字符串。ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentType(FileUtil.getContentType("."+extName));putObjectRequest.setMetadata(objectMetadata);ossClient.putObject(putObjectRequest);// 关闭OSSClient。ossClient.shutdown();return backurl+realName;}
}

FastdfsFileUpoad实现:

@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload {@Value("${fastdfs.url}")private String url;/**** 文件上传* @param buffers:文件字节数组* @param extName:后缀名* @return*/@Overridepublic String upload(byte[] buffers, String extName) {/**** 文件上传后的返回值* uploadResults[0]:文件上传所存储的组名,例如:group1* uploadResults[1]:文件存储路径,例如:M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg*/String[] uploadResults = null;try {//获取StorageClient对象StorageClient storageClient = getStorageClient();//执行文件上传uploadResults = storageClient.upload_file(buffers, extName, null);return url+uploadResults[0]+"/"+uploadResults[1];} catch (Exception e) {throw new RuntimeException(e);}}/**** 初始化tracker信息*/static {try {//获取tracker的配置文件fdfs_client.conf的位置String filePath = new ClassPathResource("fdfs_client.conf").getPath();//加载tracker配置信息ClientGlobal.init(filePath);} catch (Exception e) {e.printStackTrace();}}/**** 获取StorageClient* @return* @throws Exception*/public static StorageClient getStorageClient() throws Exception{//创建TrackerClient对象TrackerClient trackerClient = new TrackerClient();//通过TrackerClient获取TrackerServer对象TrackerServer trackerServer = trackerClient.getConnection();//通过TrackerServer创建StorageClientStorageClient storageClient = new StorageClient(trackerServer,null);return storageClient;}
}

FileUploadProxy代理实现:

@Component
@Data
@ConfigurationProperties(prefix = "upload")
public class FileUploadProxy implements ApplicationContextAware {private ApplicationContext ac;//aliyunOSSFileUpload -> mp4,aviprivate Map<String, List<String>> filemap;public String upload(MultipartFile multipartFile) throws IOException {//文件名字 1.mp4String filename = multipartFile.getOriginalFilename();//扩展名 mp4,jpgString extension = StringUtils.getFilenameExtension(filename);for (Map.Entry<String, List<String>> listEntry : filemap.entrySet()) {for (String value : listEntry.getValue()) {//匹配当前extension和当前map中对应的类型是否匹配if (value.equalsIgnoreCase(extension)) {//一旦匹配,则把key作为唯一值,从容器中获取对应实例return ac.getBean(listEntry.getKey(), FileUpload.class).upload(multipartFile.getBytes(), extension);}}}return null;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ac = applicationContext;}
}

FileController控制器实现:

@RestController
@RequestMapping(value = "/file")
public class FileController {@Autowiredprivate FileUploadProxy fileUploadProxy;/**** 文件上传* @param file* @return* @throws IOException*/@PostMapping(value = "/upload")public String upload(MultipartFile file) throws IOException {return fileUploadProxy.upload(file);}
}

2 享元模式

定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
与单例的区别:
单例是对象只能自己创建自己,整个应用中只有1个对象
享元模式根据需要共享,不限制被谁创建(有可能有多个对象实例)
优点:特定环境下,相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
缺点:为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

2.1 用户信息共享实现

案例:用户下单,会话共享

2.1.1 会话跟踪分析

当前微服务项目中,身份识别的主流方法是前端将用户令牌存储到请求头中,每次请求将请求头中的令牌携带到后台,后台每次从请求头中获取令牌来识别用户身份。
我们在项目操作过程中,很多地方都会用到用户身份信息,比如下订单的时候,要知道当前订单属于哪个用户,记录下单关键日志的时候,需要记录用户操作的信息以及用户信息,关键日志记录我们一般用AOP进行拦截操作,此时没法直接把用户身份信息传给AOP。这个时候我们可以利用享元模式实现用户会话信息共享操作。

2.1.2 代码实现

我们采用享元模式实现用户会话共享操作,要解决如下几个问题:
1、用户会话共享
2、会话多线程安全
3、订单数据用户信息获取
4、AOP日志记录用户信息获取
定义共享组件: LogComponent

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public abstract class LogComponent {/*********************************************************** 同一个线程中,记录日志时,username、sex、role相同**********************************************************///用户名字private String username;//用户性别private String sex;//用户角色private String role;/*********************************************************** 同一个线程中,记录日志时,每次访问的不同方法和参数不一样**********************************************************///操作方法private String methodName;//信息private String message;/*********************************************************** 业务操作,补充和完善methodName,args参数**********************************************************/abstract void supplementLogContent(String... args);/***** 对username、sex、role赋值[这些是同一个线程中不变的数据]*/public LogComponent(String username, String sex, String role) {this.username = username;this.sex = sex;this.role = role;}
}

享元组件逻辑操作对象: SupplementSource
SupplementSource 该对象主要用于给当前线程填充共享数据,以及变更访问方法和访问信息等信息的逻辑操作,代码如下:

public class SupplementSource extends LogComponent{/***** 填充参数* @param username*/public SupplementSource(String username, String sex, String role) {super(username, sex, role);}/***** 业务逻辑,完善不同方法的日志记录* @param args 长度为2,第1个是方法名字,第2个是方日志信息*/@Overridevoid supplementLogContent(String... args) {super.setMethodName(args[0]);super.setMessage(args[1]);}
}

多线程安全控制: ThreadUserLog
每个线程请求的时候,我们需要保障会话安全,比如A线程访问和B线程访问,他们的用户会话身份不能因为并发原因而发生混乱。这里我们可以采用ThreadLocal来实现。我们创建一个ThreadUserLog 对象,并在该对象中创建ThreadLocal<LogComponent> 用户存储每个线程的会话信息,并实现ThreadLocal<LogComponent> 的操作,代码如下:

@Component
public class ThreadUserLog {//存储线程对应的用户名日志信息private static ThreadLocal<LogComponent> userRecode = new ThreadLocal<LogComponent>();/***** 添加用户信息记录*/public void add(LogComponent logComponent){userRecode.set(logComponent);}/**** 记录方法名和参数* @param args*/public String reload(String... args){//获取对象LogComponent logComponent = userRecode.get();//设置数据logComponent.supplementLogContent(args);return logComponent.toString();}/***** 获取LogComponent*/public LogComponent get(){return userRecode.get();}/***** 移除*/public void remove(){userRecode.remove();}
}

线程会话初始化: AuthorizationInterceptor
AuthorizationInterceptor 拦截器的作用是用于初始化用户访问的时候用户的身份信息,并将身份信息存储到ThreadUserLog 的ThreadLocal 中,在用户访问方法结束,销毁ThreadUserLog 的ThreadLocal 中会话,代码如下:

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {@Autowiredprivate ThreadUserLog threadUserLog;/***** 将用户会话存储到ThreadLocal中* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {try {//获取令牌String authorization = request.getHeader("Authorization");//解析令牌if(!StringUtils.isEmpty(authorization)){Map<String, Object> tokenMap = JwtTokenUtil.parseToken(authorization);//将用户数据存入到ThreadLocal中LogComponent logComponent = new SupplementSource(tokenMap.get("username").toString(),tokenMap.get("sex").toString(),tokenMap.get("role").toString());//添加当前线程用户信息记录threadUserLog.add(logComponent);return true;}} catch (Exception e) {e.printStackTrace();}//输出令牌校验失败response.setContentType("application/json;charset=utf-8");response.getWriter().print("身份校验失败!");response.getWriter().close();return false;}/*** 移除会话信息* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {threadUserLog.remove();}
}

创建MvcConfig 来配置拦截器AuthorizationInterceptor ,代码如下:

@Component
public class MvcConfig implements WebMvcConfigurer{@Autowiredprivate AuthorizationInterceptor authorizationInterceptor;/**** 拦截器配置*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login");}
}

登录时需要生成JWT令牌:

@RestController
@RequestMapping(value = "/user")
public class LoginController {@Autowiredprivate UserService userService;/**** 登录* @param username* @param password* @return*/@PostMapping(value = "/login")public String login(String username,String password) throws Exception {//根据用户名查询(不做密码测试)User user = userService.findByUserName(username);if(user==null){return "登录失败!";}//模拟登录Map<String,Object> userMap = new HashMap<String,Object>();userMap.put("username",user.getUsername());userMap.put("name","王五");userMap.put("sex",user.getSex());userMap.put("role",user.getRole());//生成JWT令牌String token = JwtTokenUtil.generateTokenUser(UUID.randomUUID().toString(), userMap, 36000000L);return token;}
}

共享信息使用:
①AOP记录日志:创建AOP切面类LogAspect 用于记录日志,代码如下:

@Component
@Aspect
@Slf4j
public class LogAspect {@Autowiredprivate ThreadUserLog threadUserLog;/**** 记录日志*/@SneakyThrows@Before("execution(* com.learn.shop.service.impl.*.*(..))")public void logRecode(JoinPoint joinPoint){//获取方法名字和参数String methodName = joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName();//记录日志log.info(threadUserLog.reload(methodName,args(joinPoint.getArgs())));}/***** 参数获取*/public String args(Object[] args){StringBuffer buffer = new StringBuffer();for (int i = 0; i <args.length ; i++) {buffer.append("  args("+i+"):"+args[i].toString());}return buffer.toString();}
}

②添加订单获取用户信息:在添加订单方法OrderServiceImpl.add(Order order) 中,从ThreadUserLog中获取用户会话,并填充给Order,代码如下:

@Overridepublic int add(Order order) {//①设置用户名-从共享对象中获取order.setUsername(threadUserLog.get().getUsername());//修改库存int mCount = itemService.modify(order.getNum(), order.getItemId());//添加订单int addCount = orderDao.add(order);return addCount;}

3 装饰者模式

定义:动态的向一个现有的对象添加新的功能,同时又不改变其结构。它属于结构型模式。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。

3.1 结算价格嵌套运算

案例:结算价格计算,根据不同价格嵌套运算

3.1.1 订单价格结算分析

在订单提交的时候,订单价格和结算价格其实是两码事,订单价格是当前商品成交价格,而结算价格是用户最终需要支付的金额,最终支付的金额并不是一成不变,它也并不是商品成交价格,能改变结算价格的因素很多,比如满100减10元,VIP用户再减5块。订单结算金额计算我们就可以采用装饰者模式。

3.1.2 价格结算实现

实现思路分析:
1、创建接口(MoneySum),定义订单价格计算,因为所有价格波动,都是基于订单价格来波动的。
2、创建订单价格计算类(OrderPayMoneyOperation),实现MoneySum接口,实现订单价格计算。
3、创建装饰者对象(DecoratorMoneySum),以供功能扩展。
4、创建类FullMoneySum 扩展装饰者类,实现满减价格计算
5、创建类VipMoneySum ,实现VIP优惠计算。

基础接口:创建接口MoneySum ,该接口只用于定义计算订单金额的方法。

public interface MoneySum {//订单金额求和计算void sum(Order order);
}

订单金额计算类:创建类OrderPayMoneyOperation 实现订单金额的计算。

@Component(value = "orderMoneySum")
public class OrderMoneySum implements MoneySum {@Autowiredprivate ItemDao itemDao;//总金额计算@Overridepublic void sum(Order order) {//商品单价*总数量Item item = itemDao.findById(order.getItemId());order.setPaymoney(item.getPrice() * order.getNum());order.setMoney(item.getPrice() * order.getNum());}
}

装饰者类:创建装饰者类DecoratorMoneySum 供其他类扩展。

public class DecoratorMoneySum implements MoneySum {private MoneySum moneySum;public void setMoneySum(MoneySum moneySum) {this.moneySum = moneySum;}//计算金额@Overridepublic void sum(Order order) {moneySum.sum(order);}
}

满100减10元价格计算:创建类FullMoneySum 扩展装饰者类,实现满减价格计算。

@Component(value = "fullMoneySum")
public class FullMoneySum extends DecoratorMoneySum{//原来的功能上进行增强@Overridepublic void sum(Order order) {//原有功能super.sum(order);//增强moneySum(order);}//满100减5块private void moneySum(Order order){Integer paymoney = order.getPaymoney();if(paymoney>=100){order.setPaymoney(paymoney-10);}}
}

VIP优惠10元价格计算:创建类VipMoneySum ,实现VIP优惠计算。

@Component(value = "vipMoneySum")
public class VipMoneySum extends DecoratorMoneySum {//原有方法上增强@Overridepublic void sum(Order order) {//原有功能super.sum(order);//增强vipMoneySum(order);}//Vip价格优惠-5private void vipMoneySum(Order order){order.setPaymoney(order.getPaymoney()-5);}
}

支付金额计算:修改OrderServiceImpl 的add() 方法,添加订单金额以及订单支付金额的计算功能,代码如下:

//====================装饰者模式所需对象============================@Autowiredprivate MoneySum orderMoneySum;@Autowiredprivate DecoratorMoneySum fullMoneySum;@Autowiredprivate DecoratorMoneySum vipMoneySum;//====================装饰者模式所需对象============================
@Overridepublic int add(Order order) {//①设置用户名-从共享对象中获取order.setUsername(threadUserLog.get().getUsername());//装饰者模式计算金额// 1、orderMoneySum使用num*price// 2、fullMoneySum 满100减10快fullMoneySum.setMoneySum(orderMoneySum);// 3、VIP客户减5快vipMoneySum.setMoneySum(fullMoneySum);vipMoneySum.sum(order);//修改库存int mCount = itemService.modify(order.getNum(), order.getItemId());//添加订单int addCount = orderDao.add(order);return addCount;}

4 策略模式

定义:策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。
简单来说就是就定义一个策略接口,子类策略去实现该接口去定义不同的策略。然后定义一个环境(Context,也就是需要用到策略的对象)类,以策略接口作为成员变量,根据环境来使用具体的策略。
优点:
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点:
1、策略类会增多。
2、所有策略类都需要对外暴露。

4.1 根据VIP等级结算价格

案例:结算价格计算,根据Vip不同等级进行运算

4.1.1 不用VIP优惠价格分析

用户在购买商品的时候,很多时候会根据Vip等级打不同折扣,我们这里也基于真实电商案例来实现VIP等级价格制:
Vip1->原价格
Vip2->减5元
Vip3->7折

4.1.2 代码实现

定义策略接口: Strategy

public interface Strategy {//价格计算Integer payMoney(Integer payMoney);
}

定义Vip1策略: StrategyVipOne

@Component(value = "strategyVipOne")
public class StrategyVipOne implements Strategy {//普通会员,没有优惠@Overridepublic Integer payMoney(Integer payMoney) {return payMoney;}
}

定义Vip2策略: StrategyVipTwo

@Component(value = "strategyVipTwo")
public class StrategyVipTwo implements Strategy{//策略2@Overridepublic Integer payMoney(Integer payMoney) {return payMoney-5;}
}

定义Vip3策略: StrategyVipThree

@Component(value = "strategyVipThree")
public class StrategyVipThree implements Strategy{//策略2@Overridepublic Integer payMoney(Integer payMoney) {return (int)payMoney*0.7;}
}

定义策略工厂: StrategyFactory

@Data
@ConfigurationProperties(prefix = "strategy")
@Component
public class StrategyFactory implements ApplicationContextAware {//ApplicationContext//1、定义一个Map存储所有策略【strategyVipOne=instanceOne】// 【strategyVipTwo=instanceTwo】private ApplicationContext act;//定义一个Map,存储等级和策略的关系,通过application.yml配置注入进来private Map<Integer,String> strategyMap;//3、根据会员等级获取策略【1】【2】【3】public Strategy getStrategy(Integer level){//根据等级获取策略IDString id = strategyMap.get(level);//根据ID获取对应实例return act.getBean(id,Strategy.class);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {act=applicationContext;}
}

修改application.yml,增加VIP等级与beanId的映射

#策略配置
strategy:strategyMap:1: strategyVipOne2: strategyVipTwo3: strategyVipThree

LogComponent添加用户等级

SupplementSource填充参数:

public SupplementSource(String username, String sex, String role, String level) {super(username, sex, role,level);}

装饰者模式中修改VipMoneySum 的价格运算,代码如下:

 // 注入策略工厂@Autowiredprivate StrategyFactory strategyFactory;// 用户共享信息@Autowiredprivate ThreadUserLog threadUserLog;//Vip价格根据等级优惠private void vipMoneySum(Order order){// 获取优惠策略Strategy strategy = strategyFactory.getStrategy(threadUserLog.get().getLevel());// 价格计算order.setPaymoney(strategy .payMoney(order.getPaymoney()));}

5 工厂模式

定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

5.1 支付收银

案例:收银案例,根据不同支付方式,选择不同支付渠道

5.1.1 支付渠道选中分析

用户每次下单完成后,需要支付订单,支付订单会根据自身情况选择不同支付方式,后台服务会根据用户选中不同创建不同支付渠道的实例,这里创建支付渠道的实例可以采用工厂方法模式。

5.1.2 代码实现

支付接口: PayChannel 定义支付行为。

public interface PayChannel {/**** 支付*/void pay(Integer money);
}

微信支付实现: WeixinPay 实现微信支付操作,这里只模拟。

@Component("weixinPay")
public class WeixinPay implements PayChannel {@Overridepublic void pay(Integer money) {System.out.println("微信支付成功!支付金额:"+money);}
}

支付宝支付实现: AliPay 实现支付宝支付,这里只模拟。

@Component("aliPay")
public class AliPay implements PayChannel {@Overridepublic void pay(Integer money) {System.out.println("支付宝支付成功!支付金额:"+money);}
}

支付渠道映射配置:在application.yml 中配置支付渠道映射,每次从前端传入支付ID即可从配置中获取支付渠道对应Spring容器中实例的id。

#支付通道列表
pay:paymap: {"1":"weixinPay","2":"aliPay"}

支付渠道获取工厂创建:创建PayFactory 用于获取支付渠道的实例,我们这里通过映射的key获取Spring容器中实例的id值,然后从Spring容器中根据id获取对应实例,因此该工厂需要实现接口ApplicationContextAware 来获取容器。

@Data
@Component
@ConfigurationProperties(prefix = "pay")
public class PayFactory implements ApplicationContextAware{//Spring容器private static ApplicationContext applicationContext;//支付键值对信息private Map<String,String> paymap;/**** 创建支付通道,从paymap中获取对应通道的实例名字,从applicationContext获取通道实例*/public PayChannel createChannel(String key){return applicationContext.getBean(paymap.get(key),PayChannel.class);}/**** 获取容器* @param applicationContext* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {PayFactory.applicationContext = applicationContext;}
}

支付渠道调用实现:修改PayServiceImpl 的pay 方法,实现支付,代码如下:

@Service
public class PayServiceImpl implements PayService {@Autowiredprivate OrderDao orderDao;@Autowiredprivate PayFactory payFactory;/**** 支付* @param type 支付类型* @param id  订单Id*/@Overridepublic void pay(String type, String id) {//查询订单Order order = orderDao.findById(id);//通过工厂创建支付通道实例PayChannel payChannel = payFactory.createChannel(type);//执行支付payChannel.pay(order.getPaymoney());//修改订单状态 0未支付,1已支付orderDao.modifyStatus(id,1);}
}

6 状态模式

定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
优点:
1、封装了转换规则。
2、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
3、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
1、状态模式的使用必然会增加系统类和对象的个数。
2、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代
码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

6.1 根据订单状态进行不同操作

6.1.1 订单状态改变分析

在电商案例中,订单状态每次发生变更,都要执行不同的操作,这里正好可以使用状态模式。当订单完成支付的时候,我们需要立即通知商家发货,当订单执行取消的时候,我们需要执行库存回滚,如果订单已支付,还需要执行退款操作,无论是通知商家发货还是执行库存回滚,都是由订单状态决定,因此这里可以使用状态模式来实现。
我们可以先定义状态接口State ,给状态接口实现两个不同的行为,分别是发货和回滚库存并退款,把该状态对象添加到订单内部作为成员属性,当订单的state 状态改变时,触发执行不同的状态行为动作。
Order实体类:

@Data
@AllArgsConstructor
@ToString
public class Order implements Serializable {private String itemId;private String id;private Integer money;private Integer paymoney;private Integer status;private Integer num;private String username;private String couponsId;//状态改变时,执行不同的行为private State state;public Order() {this.state = null;}
}

6.1.2 代码实现

状态接口定义: State 接口,用于定义更新状态对象,同时执行相关的行为。

public interface State {/**** 变更状态* @param order*/void doAction(Order order);/**** 执行行为*/void execute();
}

发通知消息行为定义: SendMsgBehavior 用于实现给商家发送消息通知发货,这里模拟发送消息的行为。

@Component("sendMsgBehavior")
public class SendMsgBehavior implements State {@Overridepublic void doAction(Order order) {System.out.println("订单支付");order.setState(this);}@Overridepublic void execute(){System.out.println("订单变更为已支付,需要通知商家发货!");}
}

库存回滚并退款:创建ResetStoreBehavior ,用于实现订单库存回滚,并给用户退款操作,这里退款模拟相关行为。

@Component("resetStoreBehavior")
public class ResetStoreBehavior implements State {@Overridepublic void doAction(Order order) {System.out.println("订单取消");order.setState(this);}@Overridepublic void execute(){System.out.println("订单取消,执行库存回滚!");System.out.println("订单取消,执行退款!");}
}

测试:
支付订单的时候,如果支付成功,我们调用State 变更对应的状态行为,并执行相关行为,代码如下:

@Service
public class PayServiceImpl implements PayService {@Autowiredprivate OrderDao orderDao;@Autowiredprivate PayFactory payFactory;@Autowiredprivate State sendMsgBehavior;/**** 支付* @param type* @param id*/@Overridepublic void pay(String type, String id) {//查询订单Order order = orderDao.findById(id);//通过工厂创建支付通道实例PayChannel payChannel = payFactory.createChannel(type);//执行支付payChannel.pay(order.getPaymoney());//修改订单状态 0未支付,1已支付orderDao.modifyStatus(id,1);//更改订单状态sendMsgBehavior.doAction(order);//执行行为order.getState().execute();}
}

取消订单,变更状态行为,并执行相关行为:OrderServiceImpl

    @Autowiredprivate State resetStoreBehavior;/**** 取消订单* @param id*/@Overridepublic void cancelOrder(String id) {//修改订单状态Order order = orderDao.findById(id);orderDao.modifyStatus(id,2);//库存回滚+退款resetStoreBehavior.doAction(order);order.getState().execute();}

设计模式(三)——电商系统业务中常用设计模式相关推荐

  1. Athena电商系统(01)——Athena电商系统需求分析

    摘要 一.Athena电商系统需求概述 1.1 电商系统业务需求分析 总体业务流程设计 顾客在进行网上购物时将从浏览商品.挑选商品开始,然后经过结算.支付,生成一个交易订单.商家通过后台的订单管理,可 ...

  2. 聊聊电商系统中常见的9大坑,库存超卖、重复下单、物流单ABA...

    做为一名程序员,发展方向大致可以分为两个方面:一个是业务架构,一个是技术架构(中间件方向). 业务架构,取其核心关键词,主要是围绕这不同的业务场景.业务规则,完成业务系统的落地建设,为用户提供在线化的 ...

  3. 电商系统中常见的 9 大坑,你踩过没?

    做为一名程序员,发展方向大致可以分为两个方面:一个是业务架构,一个是技术架构(中间件方向). 业务架构,取其核心关键词,主要是围绕这不同的业务场景.业务规则,完成业务系统的落地建设,为用户提供在线化的 ...

  4. 软件架构-解密电商系统营销-会员模块业务

    上次说了商品,商品分类,品牌,分类的属性,规格.很多电商网站例如:京东,国美,苏宁他们的商品都是存储在redis中的,通过请求获取到的redis进行js的渲染动态的展示商品的信息. (一)营销模块 营 ...

  5. 电商系统中的SPU和SKU

    1.SPU介绍 SPU = Standard Product Unit(标准产品单元) SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗的讲,除 ...

  6. PHP电商的sku,tech| 关于电商系统中sku与spu的一个难题

    date: 2018-8-1 21:17:14 title: tech| tech| 关于电商系统中sku与spu的一个难题 description: 业务上碰到的关于电商系统中sku与spu的一个难 ...

  7. 幂等和高并发在电商系统中的使用

    在Java web项目开发中,经常会听到在做订单系统中生成订单的时候,要做幂等性控制和并发控制,特对此部分内容作出总结,在高并发场景下,代码层面需要实现并发控制:但是幂等性,其实更多的是系统的接口对外 ...

  8. 小林求职记(三)一上来就围绕电商系统层层提问,我太难了....

    前传 面试官:什么是大事务?小林哥:就是 很大...的...事务?? 小林求职记(二):说好的问基础,为啥我感觉一点也不基础呢? 二面的面试官来到来我的跟前,开始对我的简历进行了一番打量然后就开始了技 ...

  9. 大数据项目之电商数仓、业务数据介绍、电商系统表结构

    文章目录 6. 业务数据介绍 6.1 电商系统表结构 6.1.1 活动信息表(activity_info) 6.1.2 活动规则表(activity_rule) 6.1.3 活动商品关联表(activ ...

最新文章

  1. Mac上Homebrew的使用
  2. 实验室启动postgresql 8.4备忘
  3. intel219网卡的linux驱动,ubuntu16.04安装I219-V网卡驱动问题解决方法(示例代码)
  4. 使用IntentService在Service中创建耗时任务
  5. [云炬创业管理笔记]第一章测试3
  6. python以下是变量合法命名的是_Python超级详细的变量命名规则
  7. 套口机跳针修理带图_套口机维修注意事项
  8. AndroidStudio_A connection was leaked. Did you forget to close a response body?---Android原生开发工作笔记241
  9. urtlebot3启动键盘控制后 /cmd_vel 会持续输出数据,在键盘不给信号时 /cmd_vel 续输,导致此功能与导航同时启动,会导致小车自动行走时一顿一顿的
  10. HBaseRegionServer宕机数据恢复
  11. ZOJ 1203[Swordfish]
  12. 要么战胜,要么战死,绝不投降
  13. 安卓地图的实现附源码
  14. oracle中vim设置行号,vim的常用操作
  15. Android Rect相关方法
  16. Windows 7 Boot Updater 如何使用
  17. Python学习之求绝对值的几种方法
  18. 微服务理论与实践[1]-什么是微服务
  19. roce和iwarp_VIA、IB、RDMA、RoCE、iWARP、DPDK的发展与纠缠?
  20. Linux文件管理技术实践

热门文章

  1. 压倒DeFi的会是手续费?ETH算力、手续费都创新高,网络却拥堵不堪
  2. python饼图显示百分比怎么设置_python使用Matplotlib绘制饼图
  3. 软件性能测试平台,评测平台介绍及理论性能测试
  4. notepad++精美主题字体
  5. 借助Transformer,DeepMind新模型自动生成CAD草图,网友:建筑设计要起飞了
  6. deepin如何恢复出厂设置_华硕win7系统恢复出厂设置详细步骤教程 - 系统家园
  7. 1.4CAD2017绘图基础
  8. 什么是缓存一致性问题?如何解决?
  9. 【精选篇】你知道nba有哪些球队获得过总冠军?
  10. 装修客户有哪些渠道可以找:装修客户资源在哪里找:装修行业怎么找客户?