Java设计模式业务场景实战应用
一、设计模式的分类
1. 创建模式(5种)
- 单例模式(Singleton Pattern):保证一个类仅有一个对象,并提供一个访问它的全局访问点。
- 工厂模式(Factory Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化,FactoryMethod使一个类的实例化延迟到其子类。(明确地计划不同条件下创建不同实例时)
- 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。(在一个工厂里聚合多个同类产品)
- 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(例如Java中的StringBuilder)
- 原型模式(Prototype Pattern): 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。(提前创建对象工厂,使用时进行深拷贝)
2. 结构型模式(Structural Patterns)
- 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。(同一功能,不同方法,暴露出同一接口)
- 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使他们都可以独立地变化。(抽象与实现分离,可以使用方法传入函数式接口实现)
- 装饰者模式(Decorator Pattern):动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类的方式更为灵活。(核心功能扩展)
- 组合模式(Composite Pattern):将对象组合成树形结构以表示“部分-整体”的层次结构。Composite 使得客户对单个对象和复合对象的使用具有一致性。(一般用于组织架构设计)
- 外观模式(Facade Pattern):为子系统中的一组接口提供一个一致的接口。Façade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。(一般用于组合内部多个service的功能,并暴露给外界)
- 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。(用 HashMap 存储这些对象,类似spring的处理方式)
- 代理模式(Proxy Pattern):为其他对象提供一个代理以控制对这个对象的访问。
3. 行为型模式(Behavioral Patterns)
-
模版方法模式(Template Pattern):定义一个操作中的算法的骨架,而将一些步骤延迟到子类。TemplateMethod 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(通用架构的共有功能的实现,可以通过子类实现特有功能)
-
命令模式(Command Pattern):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可取消的操作。(类似于rpc的broker)
-
迭代器模式(Iterator Pattern):提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。(迭代器)
-
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。(mq,spring的event等等)
-
中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。(MVC模式,其中C就算M和V的中介)
-
备忘录模式(Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保持该状态,这样以后就可以将该对象恢复到保存的状态。(打单机游戏时的存档)
-
解释器模式(Interpreter Pattern):定义一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。(比如,判断一个字符串是否为[数字-字符-数字]形式,就可以使用解释器)
-
策略模式(Strategy Pattern):定义一系列的算法,把它们一个个封装起来,并且使他们可相互替换。本模式使得算法的变化可以独立于使用它的客户。(不同的传参,决定使用的策略)
-
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。(策略模式会控制对象使用什么策略,而状态模式会在某些策略中自动改变状态从而执行其他策略)
https://www.runoob.com/w3cnote/state-vs-strategy.html(糖果机的例子)
- 责任链模式(Chain of Responsibility Pattern):为解除请求的发送者和接收者之间的耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它。(将需要顺序执行的类,作为单向链表串联,并可任意配置执行顺序)
- 访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类别的前提下定义作用于这些元素的新操作。(不同的访问者,对不同的对象,有不同的操作时使用。访问者和对象的结构需要比较稳定)
思考:哪些场景可以使用到访问者模式?
现在,你需要做一套发票相关的系统,其中在开具发票之前,需要先通过上游的单据生成待开票明细,再根据明细进行开票,所以不同的上游单据就可以作为不同的访问者。
- 抽象访问者(Visitor)抽象访问者
- 具体访问者(ConcreteVisitor)待开票对象访问者
- 抽象元素(Element)上游单据
- 具体元素(ConcreteElement)订单、收获/送货单、对账单、开票申请单
- 对象结构(Object Structure)入口
二、设计原则
1、单一职责原则: 每个类只负责自己的事情,而不是变成万能的
2、接口隔离原则:各个类建立自己的专用接口,而不是建立万能接口
3、依赖倒转原则:面向接口编程,而不是面向实现类
4、里氏替换原则:继承父类而不去改变父类
5、开闭原则:拓展新类而不是修改旧类
6、迪米特法则:无需直接交互的两个类,如果需要交互,使用中间者
7、合成复用原则:优先组合,其次继承
三、装饰者模式
场景:现在有这样一个需求,你的产品经理想要对产品的某一些功能添加权限控制,只有开通了该功能服务的用户,才可以使用该功能。
@Component
@Aspect
@Slf4j
public class PlatformServiceCheckAspect {
@Autowired
private PlatformServiceCheckComponent platformServiceCheckComponentImpl;
@Around("@annotation(com.test.platform.authentication.annotation.OpenApiServiceActCheck)")
public Object openApiProcess(final ProceedingJoinPoint point) {
Object result = null;
try {
ServiceActCheckAspectDto aspectDto = getAspectDto(ServiceActCheckAnnoType.OpenApi, point);
//服务开通检测
result = commonServiceCheck(point, aspectDto);
} catch (BizServiceException e) {
result = new PlatformResponse<>(Constant.FPKJ_FAIL_CODE, e.getMessage(), e.toString());
} finally {
return result;
}
}
//...其他methods
}
需求改动,产品经理不满足现状,想要把权限控制拆的更加细致:
把该功能模块拆成了一个大的功能模块和诺干个小的功能模块。当使用功能时,需要同时校验大的功能模块和小的功能模块的开通情况。
@Slf4j
@Component
public class PlatformServiceCheckComponentImpl implements PlatformServiceCheckComponent {
@Autowired
private PlatformServiceRemoteService platformServiceRemoteService;
/**
* 默认:服务开通状态检测
* @param platformServiceCheckDto
*/
@Override
public void beforeMethod(PlatformServiceCheckDto platformServiceCheckDto) {
if (StringUtils.isBlank(platformServiceCheckDto.getActCode())) {
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT, "未知的开通对象!");
}
String serviceStatus = platformServiceRemoteService.getServiceStateByCodeInRedis(platformServiceCheckDto);
if (!PlatformServiceStatusType.ACTIVATION.name().equals(serviceStatus)){
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT, "未开通该服务!");
}
log.info("{}: 已开通该服务", platformServiceCheckDto.getActCode());
}
/**
* 当执行成功时,进行累加并返回接口调用次数
* 当执行失败时,直接返回调用次数
* @param platformServiceCheckDto
*/
@Override
public void afterMethod(PlatformServiceCheckDto platformServiceCheckDto, Object result) {
log.info("{}: 无需进行后置操作" + platformServiceCheckDto.getServiceType());
}
}
public abstract class PlatformServiceCheckDecorator implements PlatformServiceCheckComponent {
private PlatformServiceCheckComponent component;
public PlatformServiceCheckDecorator(PlatformServiceCheckComponent component) {
this.component = component;
}
@Override
public void beforeMethod(PlatformServiceCheckDto platformServiceCheckDto) {
component.beforeMethod(platformServiceCheckDto);
}
@Override
public void afterMethod(PlatformServiceCheckDto platformServiceCheckDto, Object result) {
component.afterMethod(platformServiceCheckDto, result);
}
}
@Slf4j
@Component
public class InvoicePlatformServiceDecorator extends PlatformServiceCheckDecorator {
@Resource
private TaxEquipmentRemoteService taxEquipmentRemoteService;
public InvoicePlatformServiceDecorator (PlatformServiceCheckComponent platformServiceCheckComponentImpl) {
super(platformServiceCheckComponentImpl);
}
@Override
public void beforeMethod(PlatformServiceCheckDto platformServiceCheckDto) {
log.info("开始检测" + PlatformServiceType.MAYCUR_INVOICE_SERVICE.getDesc() + "的开通状态");
//检测服务开通
super.beforeMethod(platformServiceCheckDto);
//检测是否绑定
String appid = taxEquipmentRemoteService.getAppidFromInternalOrHL(platformServiceCheckDto.getEquipmentCode(),
platformServiceCheckDto.getActCode(), false);
if (StringUtils.isBlank(appid)) {
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT, "税控设备未进行绑定!");
}
platformServiceCheckDto.setAppid(appid);
//检测是否在线
TaxEquipmentDetailDto taxEquipmentDetailDto = taxEquipmentRemoteService
.checkActive(platformServiceCheckDto.getEquipmentCode(), platformServiceCheckDto.getEntCode());
if (taxEquipmentDetailDto.getActiveStatus() == false) {
log.info("税控设备离线!");
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT, "税控设备离线!");
}
}
@Override
public void afterMethod(PlatformServiceCheckDto platformServiceCheckDto, Object result) {
super.afterMethod(platformServiceCheckDto, result);
}
}
四、策略模式
场景:你负责公司的发票业务,但对于发票相关的金额计算,有些开发并不了解,导致在线上出现了金额计算错误或者尾差问题
金额计算的方式
@Component
@Order(1)
public class BaseOnPriceIncludedTax implements CalculateStrategy {
private static final int PRE_INVOICE_INDEX = 2;
@Override
public BigDecimal doPriceIncludedTax(CalculateSource source, CalculateTarget target) {
return source.getPriceIncludedTax().setScale(6, RoundingMode.HALF_UP);
}
@Override
public BigDecimal doPriceExcludedTax(CalculateSource source, CalculateTarget target) {
return target.getPriceIncludedTax().divide(source.getTaxPercent().add(BigDecimal.ONE), 6, RoundingMode.HALF_UP);
}
@Override
public BigDecimal doAmountExcludedTax(CalculateSource source, CalculateTarget target) {
return target.getAmountIncludedTax().divide(source.getTaxPercent().add(BigDecimal.ONE), 2, RoundingMode.HALF_UP);
}
@Override
public BigDecimal doAmountIncludedTax(CalculateSource source, CalculateTarget target) {
return source.getPriceIncludedTax().multiply(source.getQuantity()).setScale(2, RoundingMode.HALF_UP);
}
@Override
public BigDecimal doTaxAmount(CalculateSource source, CalculateTarget target) {
return target.getAmountIncludedTax().subtract(target.getAmountExcludedTax()).setScale(2, RoundingMode.HALF_UP);
}
@Override
public int getPreInvoiceIndex() {
return PRE_INVOICE_INDEX;
}
@Override
public boolean support(CalculateSource source) {
return source.getPriceIncludedTax() != null;
}
@Override
public CalculateTarget doAllOnSpecifiedOrder(CalculateSource source) {
CalculateTarget target = new CalculateTarget();
target.setPriceIncludedTax(doPriceIncludedTax(source, target));
target.setPriceExcludedTax(doPriceExcludedTax(source, target));
target.setAmountIncludedTax(doAmountIncludedTax(source, target));
target.setAmountExcludedTax(doAmountExcludedTax(source, target));
target.setTaxAmount(doTaxAmount(source, target));
return target;
}
}
@Component
public class CalculatePriceAndAmountContext {
/**
* @Order 控制了注入顺序
*/
@Autowired
private List<CalculateStrategy> calculateStrategies;
/**
* 计算入口
*
* @param source
* @return
*/
public CalculateTarget calculate(CalculateSource source) {
if (source.getAmountIncludedTax() == null && source.getPriceExcludedTax() == null
&& source.getAmountExcludedTax() == null && source.getPriceIncludedTax() == null) {
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT, "含税单价,未税单价,含税金额,未税金额至少填写一个");
}
for (CalculateStrategy calculateStrategy : calculateStrategies) {
if (calculateStrategy.support(source)) {
return calculateStrategy.doAll(source);
}
}
throw new BizServiceException(EErrorCode.INVALID_ARGUMENT,
"非法的单价和金额,您传入的:" + JsonUtil.stringify(source));
}
}
五、模板模式
场景1:你是公司的架构师,想要将公司的业务代码进行整合,于是想要写一套通用的业务处理模板
public abstract class AbstractTemplate<REQUEST extends TemplateRequest<?>, RESPONSE extends TemplateResponse<?>> {
/**
* 模板入口
*
* @param request 请求对象
* @return 响应对象
*/
public final RESPONSE execute(REQUEST request) {
if (!request.isSkipValidator()) {
ValidatorUtil.validate(request.getBody());
}
checkRequest(request);
preProcess(request);
try {
return process(request);
} catch (Throwable e) {
exceptionHandle(request);
throw e;
}
}
/**
* 请求校验
*/
protected void checkRequest(REQUEST request) {
}
/**
* 请求预处理
*/
protected void preProcess(REQUEST request) {
}
/**
* 业务处理
*/
protected abstract RESPONSE process(REQUEST request);
/**
* 异常处理
*/
protected void exceptionHandle(REQUEST request) {
}
}
场景2:产品想要做一个数据权限的功能,需要对页面上所有的数据都进行权限管理,包括数据的负责人是否为当前用户,当前用户是否为提单人等等
六、责任链
场景:某港需要做一个计算引擎,对下属所有的员工进行自动分配每日的工作,当输入员工的身份信息时,输出今天要做的所有事情
概念:
1、变化对:业务场景,把所有的物体颗粒用变化对表示
2、计算流程:细化,转换,价值,初运算,末运算(每一步都设计一个人工智能)
其他场景:线索新建、导入等操作时,都需要进行去重、分配、回收等操作。(线索分配给销售,如果销售长时间不跟进就要回收,然后分配给其他销售)
责任链另一种实现形式实例代码(参考菜鸟教程)
public abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
//责任链中的下一个元素
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}