电商系统设计模式实战

1 代理模式

案例:根据文件类型,将文件存储到不同服务

代理模式:给一个对象创建一个代理对象,通过代理对象可以使用该对象的功能。

CGLib和JDK是代理模式实现的技术方案。

1.1 文件服务应用

代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,如下图文件上传代理服务,针对一些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如商品视频介绍,我们可以把它存储到第三方OSS。

用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码层面充分运用了代理模式,在架构层面也充分运用了代理模式。

a18911d8835253c6b9084f0e70a6c7a7.png

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

1.2.1 实现分析

基于代理模式,我们实现文件上传分别路由到 aliyunOSS 和 FastDFS ,用例图如下:

7fef0253746c13da6dd202a34fe66231.png

讲解:

1、FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现。

2、AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件。

3、FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件。

4、FileUploadProxy是代理对象,供用户访问,调用了FileUpload的文件上传方法,为用户提供不同文件上传调用。

5、FileController是控制器,用于接收用户提交的文件,并调用代理FileUploadProxy实现文件上传。

1.2.2 代码实现

bootstrap.yml配置:

37e2e97ef4e3c846c6e8087589565e62.png

84fc0d0b22d4c4e65146e1e67ea70e7f.png

FileUpload接口定义:

bf795b66b36422535127185a015ad05c.png

AliyunOSSFileUpload实现:

a3391b29e08b7b6b29164e4536435550.png

e90ead9c8d296f82d150869b2097a0be.png

FastdfsFileUpoad实现:

d0c53f5cb68a2721bd5a178f6553b826.png

505a42078dd1adeee63b1ad36c6a6b7c.png

FileUploadProxy代理实现:

3e0f0b7011bea87f7a3fcd9aaf179329.png

9e6eae027bf18dc012f41e12d2506f6f.png

FileController控制器实现

3b65b06b447f9563a368969e90c7cda8.png

2 享元模式

定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式和单利的区别:

单例是对象只能自己创建自己,整个应用中只有1个对象

享元模式根据需要共享,不限制被谁创建(有可能有多个对象实例)

优点:

特定环境下,相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点:

为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性

2.1 享元模式实战

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

2.1.1 会话跟踪分析

会话跟踪,如果是传统项目用Session或者是Cookie,全项目通用,但在微服务项目中,不用Session也不用Cookie,所以想要在微服务项目中实现会话跟踪,是有一定难度的。

当前微服务项目中,身份识别的主流方法是前端将用户令牌存储到请求头中,每次请求将请求头中的令牌携带到后台,后台每次从请求头中获取令牌来识别用户身份。

我们在项目操作过程中,很多地方都会用到用户身份信息,比如下订单的时候,要知道当前订单属于哪个用户,记录下单关键日志的时候,需要记录用户操作的信息以及用户信息,关键日志记录我们一般用AOP进行拦截操作,此时没法直接把用户身份信息传给AOP。这个时候我们可以利用享元模式实现用户会话信息共享操作。操作流程如下图:

3d9f0bbb98260b39ed467fe2eb3daa7e.png

2.1.2 会话共享案例实现

基于上面的分析,我们采用享元模式实现用户会话共享操作,要解决如下几个问题:

d4e8a010b3d79bd64eb17c06b501192a.png

定义共享组件:LogComponent

LogComponent 里面定义了每个线程中不变的用户身份信息 username 、 role 、 sex ,其他的是可能存在变化的数据,例如用于做日志记录的 methodName 、 message 。

2f54d2c9b8020216c9c266bd671296b9.png

享元组件逻辑操作对象:SupplementSource

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

9b8789618d0b4e4a165f7376c6ae0add.png

多线程安全控制:ThreadUserLog

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

96bb113deec77cdb57a4745aba124500.png

c18f9d86ac0e563b0db95c09641cb1ba.png

线程会话初始化:AuthorizationInterceptor

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

1e76952adfd9341eb9a6145445936331.png

c09f5c47b69ee83adaf1aeed69cb91b6.png

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

共享信息使用:

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

4206510f830b2d52207ecb6062e6b4e7.png

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

5435a8f2fc7f6e0fb9ad96441c2836f7.png

添加订单,日志输出可以看到调用添加订单和修改库存时,都记录了日志,并且获取了用户会话,效果如下:

9905752f6d1529de35e7b8bb6bdabdf8.png

加的订单数据库数据中也拥有用户信息,效果如下:

431dd346f980eb5278ef81ce17287874.png

3 装饰着模式

定义:动态的向一个现有的对象添加新的功能,同时又不改变其结构。它属于结构型模式。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能

缺点:多层装饰比较复杂。

3.1 装饰者模式实战

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

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

dd6028144a237d6fd0b1cac6ee2f6b01.png

3.1.2 装饰者模式价格运算实现

实现思路分析:

fb30c7be615267455bc52d338b072c7e.png

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

35cfadbd95d7e3d588aef287a975480b.png

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

1a4910b6836248a39df499e3aface036.png

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

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

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

a075f81734f149eb2ea034fe17d5c696.png

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

2c4064c58a90962feb4e6e3992aa7c6c.png

测试效果:

测试数据中,我们选择购买1件商品,当前登录用户为王五,拥有5个金币,当前购买的商品id=1,商品单价是150元,满减100,VIP优惠5元,最终支付135元。

908d0a89c20625fcc51312f2327ddf6b.png

测试生成的订单如下:

a464b71e51e2b76dfee41d229a37ce80.png

不仅如此,我们可以随时撤掉满减和Vip优惠功能。

4 策略模式

定义:策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。

简单来说就是就定义一个策略接口,子类策略去实现该接口去定义不同的策略。然后定义一个环境(Context,也就是需要用到策略的对象)类,以策略接口作为成员变量,根据环境来使用具体的策略。

优点:

75df3281beaff1ab5ea6f987f9d89980.png

缺点:

0cf73be613dd4f07d0744a72e77eda9e.png

4.1 策略模式实战

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

4.1.1 不同VIP优惠价格分析

17281c112d3b44b010c4ea06f7c00243.png

用户在购买商品的时候,很多时候会根据Vip等级打不同折扣,尤其是在线商城中体现的淋漓尽致。我们这里也基于真实电商案例来实现VIP等级价格制:

6e99ece3fdb4b14aab418843482543bb.png

4.1.2 代码实现

定义策略接口:Strategy

db1276fffe8b97ca0320c8f7a79631e7.png

定义Vip0策略:StrategyVipOne

a59e9ff5ad64fb5b193d13e68f44ea47.png

定义Vip1策略: StrategyVipTwo

802d7838b869fea400e8b5740fb7a471.png

定义Vip2策略:StrategyVipThree

a230d6a7c09dd9f0ccf67468c079569c.png

定义Vip3策略:StrategyVipFour

eb76c438bf66742759a431daf51f7816.png

定义策略工厂:StrategyFactory

4067a9f40b01c202766976f1a9a1bdaa.png

等级策略配置:修改application.yml,将如下策略配置进去

9b9447eff56c2d096ce455f14c5bfc30.png

等级控制:修改 UserHandler 添加等级属性

838fa08d941e8bdea395893d58defb6b.png

修改 UserHandlerShare 定义等级,代码如下:

f047d4c12d2cae99e2a9e21a8ef482ea.png

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

805ad99f908169ca856df6e2b2460ae6.png

测试:

d398b726569b3b1bfc7ee930b1a294d7.png

5 工厂模式

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

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

5.1 工厂模式实战

5.1.1 支付渠道选中分析

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

48bac6ec678a1a1ee82c68153dbef3ce.png

5.1.2 代码实现

工厂方法在SpringBoot中如果在用工厂的同时又出现了new,那绝对是一个败笔。在SpringBoot中,几乎所有对象

都可以直接交给Spring容器管理,它天然已经是一个工厂对象,因此在SpringBoot项目中用工厂模式,再频繁new对象反而不妥。那么工厂模式究竟怎么用在SpringBoot项目的支付场景呢?其实很简单,如果我们要选择一个支付渠道,而项目中有100种支付渠道,那这个时候无疑要写100个 @Autowired 注入100个支付渠道,并且需要做100种判断,这个时候我们可以用工厂模式取代我们判断,直接根据我们配置的映射关系从Spring容器中拿到对应支付渠道的实例,代码量将大大减少,这块是使用工厂的另一种妙用

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

3ffca7fb0062ad834478d9bd80bc655d.png

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

d26b989efb1a56a6d05a1eac9f9fb09a.png

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

5efa0e21a3f29f0845cad95e6f40e3e1.png

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

5fe26fe00054277a7335492e5cc10b7c.png

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

289da258cf774b7e4d3d96b33c9d9b47.png

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

50ae131d9975be5542e28adf046f1243.png

测试:当我们选中支付渠道为1(微信支付)或者2(支付宝支付)的时候,都能争取获取对应的渠道实例。

34d01d4c82c20c093434ecb02bbba22c.png

6 状态模式

案例:订单不同状态,执行不同操作

定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

优点

141ec66beeff546a10a956cdb85568f1.png

缺点

4f9325e8cca721d942b43ac97c70610a.png

6.1 状态模式实战

6.1.1 状态模式案例分析

在电商案例中,订单状态每次发生变更,都要执行不同的操作,这里正好可以使用状态模式。当订单完成支付的时候,我们需要立即通知商家发货,当订单执行取消的时候,我们需要执行库存回滚,如果订单已支付,还需要执行退款操作,无论是通知商家发货还是执行库存回滚,都是有订单状态决定,因此这里可以使用状态模式来实现

我们可以先定义状态接口 State ,给状态接口实现两个不同的行为,分别是发货和回滚库存并退款,把该状态对象添加到订单内部作为成员属性,当订单的 state 状态改变时,触发执行不同的状态行为动作。

54173c9f1a0de110329484d4b3470996.png

5.1.2 案例实现

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

5a4e1a7d44182e36f2269ee46620507d.png

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

1283716af975fcab4bf889c9fbc4d80f.png

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

为。

d0aac5a005aae255d869f9abe4f0d62f.png

测试:

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

c363141d97cf80862f013cc25bfffefa.png

测试结果如下:

7480529fd3955a18dc3bfcd09936bb6e.png

 
  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值