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

目录

    • 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<String, List<String>> filemap;
    public String upload(MultipartFile multipartFile) throws IOException {
        //文件名字 1.mp4
        String filename = multipartFile.getOriginalFilename();
        //扩展名 mp4,jpg
        String 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;
    }
    @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);
    }
}

享元组件逻辑操作对象: 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]);
    }
}

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;
    }
}

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

@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 {
    @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<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
     */
    @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<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 {

    @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 <args.length ; i++) {
            buffer.append("  args("+i+"):"+args[i].toString());
        }
        return buffer.toString();
    }
}

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

@Override
    public 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 {
    @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 StrategyVipTwo 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<Integer,String> 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添加用户等级
设计模式(三)——电商系统业务中常用设计模式_第1张图片
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<String,String> 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();
    }

你可能感兴趣的:(#,设计模式,设计模式,spring)