设计模式(三) 您所在的位置:网站首页 java电子商务网站 设计模式(三)

设计模式(三)

2023-08-25 08:35| 来源: 网络整理| 查看: 265

目录 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,avi fastdfsFileUpload: png,jpg #FastDFS配置 fastdfs: url: http://192.168.XXX.XXX:XXX/ #aliyun aliyun: oss: endpoint: oss-cn-beijing.aliyuncs.com accessKey: a7i6rXXXXXJdYX2 accessKeySecret: MeSZPybPHXXXXXXXXXXXXEaUbfRtdH8gl4 bucketName: sklll key: video/ backurl: https://sklll.XXXXXXXXXXX.aliyuncs.com/video/ #访问地址配置 spring: application: name: seckill-goods datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: 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 */ @Override public String upload(byte[] buffers,String extName) { String realName = UUID.randomUUID().toString()+"."+extName ; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret); // 表示上传文件到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 */ @Override public 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创建StorageClient StorageClient storageClient = new StorageClient(trackerServer,null); return storageClient; } }

FileUploadProxy代理实现:

@Component @Data @ConfigurationProperties(prefix = "upload") public class FileUploadProxy implements ApplicationContextAware { private ApplicationContext ac; //aliyunOSSFileUpload -> mp4,avi private Map filemap; public String upload(MultipartFile multipartFile) throws IOException { //文件名字 1.mp4 String filename = multipartFile.getOriginalFilename(); //扩展名 mp4,jpg String extension = StringUtils.getFilenameExtension(filename); for (Map.Entry 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; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ac = applicationContext; } }

FileController控制器实现:

@RestController @RequestMapping(value = "/file") public class FileController { @Autowired private 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个是方日志信息 */ @Override void supplementLogContent(String... args) { super.setMethodName(args[0]); super.setMessage(args[1]); } }

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

@Component public class ThreadUserLog { //存储线程对应的用户名日志信息 private static ThreadLocal userRecode = new ThreadLocal(); /**** * 添加用户信息记录 */ 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 { @Autowired private ThreadUserLog threadUserLog; /**** * 将用户会话存储到ThreadLocal中 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { //获取令牌 String authorization = request.getHeader("Authorization"); //解析令牌 if(!StringUtils.isEmpty(authorization)){ Map 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 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { threadUserLog.remove(); } }

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

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

登录时需要生成JWT令牌:

@RestController @RequestMapping(value = "/user") public class LoginController { @Autowired private 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 userMap = new HashMap(); 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 { @Autowired private 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 //①设置用户名-从共享对象中获取 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 { @Autowired private ItemDao itemDao; //总金额计算 @Override public 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; } //计算金额 @Override public void sum(Order order) { moneySum.sum(order); } }

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

@Component(value = "fullMoneySum") public class FullMoneySum extends DecoratorMoneySum{ //原来的功能上进行增强 @Override public 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 { //原有方法上增强 @Override public void sum(Order order) { //原有功能 super.sum(order); //增强 vipMoneySum(order); } //Vip价格优惠-5 private void vipMoneySum(Order order){ order.setPaymoney(order.getPaymoney()-5); } }

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

//====================装饰者模式所需对象============================ @Autowired private MoneySum orderMoneySum; @Autowired private DecoratorMoneySum fullMoneySum; @Autowired private DecoratorMoneySum vipMoneySum; //====================装饰者模式所需对象============================ @Override public 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 { //普通会员,没有优惠 @Override public Integer payMoney(Integer payMoney) { return payMoney; } }

定义Vip2策略: StrategyVipTwo

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

定义Vip3策略: StrategyVipThree

@Component(value = "strategyVipThree") public class StrategyVipThree implements Strategy{ //策略2 @Override public 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 strategyMap; //3、根据会员等级获取策略【1】【2】【3】 public Strategy getStrategy(Integer level){ //根据等级获取策略ID String id = strategyMap.get(level); //根据ID获取对应实例 return act.getBean(id,Strategy.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { act=applicationContext; } }

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

#策略配置 strategy: strategyMap: 1: strategyVipOne 2: strategyVipTwo 3: strategyVipThree

LogComponent添加用户等级 在这里插入图片描述 SupplementSource填充参数:

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

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

// 注入策略工厂 @Autowired private StrategyFactory strategyFactory; // 用户共享信息 @Autowired private 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 { @Override public void pay(Integer money) { System.out.println("微信支付成功!支付金额:"+money); } }

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

@Component("aliPay") public class AliPay implements PayChannel { @Override public 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 paymap; /*** * 创建支付通道,从paymap中获取对应通道的实例名字,从applicationContext获取通道实例 */ public PayChannel createChannel(String key){ return applicationContext.getBean(paymap.get(key),PayChannel.class); } /*** * 获取容器 * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { PayFactory.applicationContext = applicationContext; } }

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

@Service public class PayServiceImpl implements PayService { @Autowired private OrderDao orderDao; @Autowired private PayFactory payFactory; /*** * 支付 * @param type 支付类型 * @param id 订单Id */ @Override public 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 { @Override public void doAction(Order order) { System.out.println("订单支付"); order.setState(this); } @Override public void execute(){ System.out.println("订单变更为已支付,需要通知商家发货!"); } }

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

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

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

@Service public class PayServiceImpl implements PayService { @Autowired private OrderDao orderDao; @Autowired private PayFactory payFactory; @Autowired private State sendMsgBehavior; /*** * 支付 * @param type * @param id */ @Override public 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

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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有