您的位置:首页 > 娱乐 > 八卦 > 业务代码插件式开发实践

业务代码插件式开发实践

2024/12/23 7:38:14 来源:https://blog.csdn.net/weixin_41866717/article/details/140060674  浏览:    关键词:业务代码插件式开发实践

在学习编程初期,会接触到设计模式的概念:23种设计模式,单例模式,策略模式,… 。接触业务研发后,对设计模式的使用和实践有了更深的见解。

使用设计模式是目的为了更高效的支撑业务诉求,如何在保证代码质量的情况下变更/扩展现有功能,这里的代码质量可分为健壮性和可读性两大类,分别包含以下方面:

  • 健壮性:并发控制、事务、单点变更、性能、可测试
  • 可读性:业务语义、圈复杂度、认知复杂度、coding style

往往在变更/扩展一块功能时,最耗时费力的不是功能编码,而是待开发功能对已有功能影响的梳理。代码质量的好坏对该过程起着决定性作用,进一步与系统Bug率也有着强关联。

但在接触到具体业务代码时,我们往往以“屎山”代称,这里有两类原因:

  1. 业务处于扩张期,业务形态未完全定型,业务需求量大且急,此时代码质量并不是首要考虑因素,而是要快速支持业务。

提升代码质量不是目的,而是手段。最终目的都是为了支撑业务。

  1. 研发人员没有意识,研发团队没有规范且无强卡点

不设置强卡点的规范都不是规范

本文讨论了一些业务研发过程中常用的插件式开发形式,以及衍生出常用的两种设计模式:策略模式和责任链模式。

插件式开发

在说明设计模式之前,先了解以下插件式开发,插件式开发对于已有功能提供了一种扩展可能,同时在变更已有功能时不会因为逻辑耦合导致影响其他功能。

Spring官方提供了一种插件组件:Spring Plugin

在业务开发中结合Spring也可以完成可插拔的设计,我将它成为supports模式。

比如这里的用户触达场景,用户可以通过短信、邮件、站内信方式接受通知,不同的途径会生成不同的消息体。因此我们有:

// 通知方式
enum MessageType{ SMS,EMAIL,APP }// 用于生成消息的输入上下文
class MessageContext {...}

在设计消息生成接口时,我们添加一个supports方法表示该实现类支持的生成方式:

public interface MessageGenerate {// 生成消息字符串方法String generate(MessageContext context);// 支持的消息类型boolean supports(MessageType messageType);
}

这样,支持生成何种类型消息的控制权就由实现类自行决定,比如我需要实现站内信的支持:

class APPMessageGenerate implements MessageGenerate {@Overridepublic String generate(MessageContext context) {// ...return "";}@Overridepublic boolean supports(MessageType messageType) {return Objects.equals(messageType, MessageType.APP);}
}

再比如,这里的短信和邮件使用一种格式,那我们只需要一个实现类即可:

class SMSEmailMessageGenerate implements MessageGenerate {@Overridepublic String generate(MessageContext context) {// ...return "";}@Overridepublic boolean supports(MessageType messageType) {return Objects.equals(messageType, MessageType.SMS)|| Objects.equals(messageType, MessageType.EMAIL);}
}

这样的设计在Spring Security中尤为常见,可以参看:Spring Security 实践

策略模式

在此基础上,构建一个门面可以实现策略模式:

@Component
class MessageFacade{@Autowiredprivate List<MessageGenerate> generators;// 根据MessageType生成对应的消息public String generateByMessageType(MessageType messageType, MessageContext context) throws OperationNotSupportedException {return generators.stream().filter(message -> message.supports(messageType)).findFirst().map(generator -> generator.generate(context)).orElseThrow(OperationNotSupportedException::new);}
}

在使用时直接调用MessageFacade#generateByMessageType即可,后续扩展,添加实现类即可,Spring可自动注入MessageGenerate接口的所有实现类。

责任链模式

另外一个case,这里我想把所有消息类型的消息都拼接在一起,并且指定拼接顺序,我们可以构建一个责任链,不同的类型按顺序生成不同的消息,此时我们需要依赖顺序,
MessageGenerate可以继承Spring的org.springframework.core.Ordered 接口:

// 继承自org.springframework.core.Ordered接口
public interface MessageGenerate extends Ordered {...
}
// 实现类实现getOrder方法
class APPMessageGenerate implements MessageGenerate {...// 值越小顺序越靠前@Overridepublic int getOrder() {return 0;}
}

或不需要继承Ordered接口,在实现类注解 Spring的org.springframework.core.annotation.Order / java的javax.annotation.Priority

// 实现类注解org.springframework.core.annotation.Order,值越小顺序越靠前
@Order(-100)
class APPMessageGenerate implements MessageGenerate {...
}

在门面中构建顺序责任链:

@Component
class MessageFacade{// 责任链@Autowiredprivate List<MessageGenerate> generators;// 排序@PostConstructprivate void init() {AnnotationAwareOrderComparator.sort(generators);}public String generateAndConcatMessage(MessageContext context){StringBuilder sb = new StringBuilder();generators.forEach(generator -> sb.append(generator.generate(context)));return sb.toString();}}

进一步,如果需要有复杂且可复用的support逻辑,可以单独抽象出一个匹配器接口:

interface MessageMatcher {boolean matches(MessageContext context);
}
// 这里以类型匹配为例
@RequiredArgsConstructor
class MessageTypeMatcher implements MessageMatcher {private final MessageType targetMessageType;@Overridepublic boolean matches(MessageContext context) {return Objects.equals(targetMessageType, context.getMessageType());}
}

这样实现类可以将匹配逻辑委托给MessageMatcher,实现逻辑解耦:

@Order(-100)
class APPMessageGenerate implements MessageGenerate {private final MessageMatcher messageMatcher = new MessageTypeMatcher(MessageType.APP);@Overridepublic String generate(MessageContext context) {// ...return "";}// 这里我们将support的输入扩大为整个context@Overridepublic boolean supports(MessageContext messageType) {return messageMatcher.matches(messageType);}}

整个UML图如下:

在这里插入图片描述

但是如果我们要在执行过程中控制是否继续这个责任链呢?这里Spring Security给出的解决方式是,在接口下构造一个抽象类记录责任链执行进度,实现类每次执行完都要调用super().generate(...)方法表示继续这个责任链,否则不继续。这样要求generate方法返回值必须为void,因此,将输出结果作为责任链入参传入,在执行过程中填充,其接口变为:

interface MessageGenerate {// 生成消息字符串方法void generate(MessageInputContext inputContext, MessageOutputContext outputContext);// 支持的消息类型boolean supports(MessageInputContext context);
}class MessageOutputContext {List<String> messages;
}

进一步,我们可以利用void返回值,让其返回boolean值表示是否继续责任链:

interface MessageGenerate {// boolean表示是否继续责任链boolean generate(MessageInputContext inputContext, MessageOutputContext outputContext);boolean supports(MessageInputContext context);
}

这样最终的Facade中的逻辑为:

@Component
class MessageFacade{@Autowiredprivate List<MessageGenerate> generators;@PostConstructprivate void init() {AnnotationAwareOrderComparator.sort(generators);}public MessageOutputContext generateMessages(MessageInputContext inputContext){MessageOutputContext outputContext = new MessageOutputContext();for (MessageGenerate generator : generators) {// 返回为false表示不继续,直接breakif (!generator.generate(inputContext, outputContext)) {break;}}return outputContext;}
}

弊端

可以看到这里所有实现类共用了一个Context,会造成一个巨大的类,内部字段何时填充,何时使用,使用目的,使用方都会变得不清晰。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com