JavaWeb可扩展性之模板方法的运用

2024-01-28 22:32

本文主要是介绍JavaWeb可扩展性之模板方法的运用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

服务编织时用模板方法模式是一种非常实用技巧,通过模板方法定义出服务基本操作、日志、异常处理等,也方便做限流、报警、流量统计等。这里的可扩展性体现在,当需要实现新添加的服务时,只需要套用模板,实现差异点就可以了。当然模板对可扩展点的定义和粒度都会影响具体的效果。

以API服务的实现为例,实现一个简单模板,有基本的日志、异常处理,代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;// API服务实现模板
public abstract class ApiServiceTemplate<R, P> {protected Logger logger = LoggerFactory.getLogger(getClass());// 业务逻辑入口protected abstract R process() throws BizException;// 接口参数protected abstract P getParam();// 模板执行入口public Response<R> execute() {long startTime = System.currentTimeMillis();logger.info("Enter service, params:{}", getParam().toString());try {R result = process();return new Response<R>(SystemCode.BIZ_SUCCESS, result);} catch (BizException e) {logger.error("Api sevice execute Bizexception", e);return new Response<R>(SystemCode.BIZ_FAILED, null);} catch (Throwable e) {logger.error("Api sevice execute error", e);return new Response<R>(SystemCode.BIZ_FAILED, null);} finally {long totalTime = System.currentTimeMillis() - startTime;if (totalTime > 200) {logger.warn("This method used too long time, please check and optimize it:{}ms", totalTime);}}}
}// 其中Response、SystemCode和BizException的代码我删掉了,只突出重点

针对一些具体的业务流程,模板方法的作用更明显,业务的具体实现是应用的核心代码,这部分可复用的价值很高,但相应地,问题也很多。主要有两点:

问题一:合作开发成本高。别人实现的带有强烈主观意愿的设计思路,往往都不那么好理解。且不好的设计尤其是各种“专属设定”往往会隐藏着坑!我曾经接手一个应用,服务实现对流程和操作抽象度很高,甚至BO都是继承了N层的接口,结果来来回回看好几遍都不清楚层次结构,每层的定位是什么。

问题二:盲目复用和多层复用会导致代码后期维护非常复杂。看下面这个吃牛肉的例子:

/*** Created by song on 2019/1/5.*/// 错误示范
public interface Eat {// 吃牛肉public void eatBeaf();}// 一般吃法,作为基本实现
abstract class NormalEat implements Eat{public void eatBeaf() {// 第一步,做熟cookBeaf();// 第二步,吃牛肉eatTheBeaf();}protected void cookBeaf() {System.out.println("roast beaf");}protected void eatTheBeaf() {System.out.println("eat with hand");}}// 西式吃法
class WestEat extends NormalEat{@Overrideprotected void eatTheBeaf() {System.out.println("eat with knife & folk");}}// 东方吃法
class EestEat extends NormalEat{@Overrideprotected void cookBeaf() {System.out.println("hot pot beaf");}@Overrideprotected void eatTheBeaf() {System.out.println("eat with chopsticks");}}// 中式吃法
class ChineseEat extends EestEat{@Overrideprotected void cookBeaf() {super.cookBeaf();// 中式吃法加很多辣椒System.out.println(" add plenty of chilli");}}// 日式吃法
class JapaneseEat extends EastEat{@Overrideprotected void eatTheBeaf() {talkBeforeEat();super.eatTheBeaf();}protected void talkBeforeEat() {// 日本人吃之前先说:"我开动了"System.out.print("いただきます");}}// 日本北海道吃法
class JapaneseBhdEat extends JapaneseEat{@Overrideprotected void talkBeforeEat() {// 北海道人吃之前不说"我开动了"(我胡诌的)}}// 日本北海道札幌市吃法
class JapaneseBhdZhEat extends JapaneseBhdEat{@Overrideprotected void cookBeaf() {super.cookBeaf();// 煮牛肉火锅加咸鱼System.out.println("add salt fish");}}

例子中讲牛肉的吃法,最基础实现NormalEat中定义了基本实现分两步:做牛肉cookBeaf和吃牛肉eatTheBeaf,到“日本北海道”吃法中,由于复用了父类JapaneseEat的eatTheBeaf和祖父类EastEat的cookBeaf,想知道北海道是怎么吃的,就需要一层层查上去。这是个非常简单的例子,实际应用中的业务场景会复杂的多,层数如果很多,而中间层又有很多差异化的“共性实现”,那么将是一个灾难,这样一层层集成下去,结果就是北海道札幌市鬼子村的松尾桑在做牛肉和吃牛肉之间做了什么,没有人能说清楚,也不知道他有没有说“我开动了”!

那么怎么解决这个问题呢?

1)不能分层太多,最多不能超过3层,比如北海道札幌市鬼子村的松尾桑吃牛肉的方法,就用一个和北海道平级的实现,虽然这一层的实现变多了,但很清晰,最多找两层就清楚所有实现了。

2)一些共用实现方法从模板里拆出来,作为独立完整的小服务,调用出只关心出入口。这样既使代码结构清晰,又使得服务的实现更加稳定且便于维护扩展。

对上例做修改如下:

// 再来一顿,改进版
public interface EatAgain {// 吃牛肉public void eatBeaf();
}// 服务模板抽象
abstract class AbsEatAgain implements EatAgain{// 做牛肉方法public abstract CookBeafHandler getCookBeafHandler();// 吃牛肉方法public abstract EatTheBeafHandler getEatTheBeafHandler();public void eatBeaf() {// 第一步,做熟getCookBeafHandler().doIt();// 第二步,吃牛肉getEatTheBeafHandler().doIt();}}// 做牛肉方法
interface CookBeafHandler {// 做牛肉public void doIt();}// 吃牛肉方法
interface EatTheBeafHandler {// 吃牛肉方法public void doIt();}// 一般吃法,作为基本实现
class NormalEat extends AbsEatAgain{public CookBeafHandler getCookBeafHandler() {return new BaseCookBeafHandler();}public EatTheBeafHandler getEatTheBeafHandler() {return new BaseEatTheBeafHandler();}
}// 西式吃法
class WestEat extends NormalEat{public EatTheBeafHandler getEatTheBeaf() {return new KnifeFolkEatTheBeafHandler();}}// 东方吃法
class EestEat extends NormalEat{public CookBeafHandler getCookBeafHandler() {return new HotPotCookBeafHandler();}public EatTheBeafHandler getEatTheBeafHandler() {return new EatWithChopsticksHandler();}}// 中式吃法
class ChineseEat extends NormalEat {public CookBeafHandler getCookBeafHandler() {return new ChilliCookBeafHandler();}
}// 日式吃法
class JapaneseEat extends NormalEat {public EatTheBeafHandler getEatTheBeafHandler() {return new TalkAndEatTheBeafHandler();}}// 日本北海道吃法
class JapaneseBhdEat extends NormalEat {// 可以直接用NormalEat,因为跟基本吃法是一样的
}// 日本北海道札幌市吃法
class JapaneseBhdZhEat extends NormalEat {public CookBeafHandler getCookBeafHandler() {return new SaltFishCookBeafHandler();}
}

所有的Handler:

// 基本做牛肉方法
class BaseCookBeafHandler implements CookBeafHandler{public void doIt() {System.out.println("roast beaf");}
}// 基本吃牛肉方法
class BaseEatTheBeafHandler implements EatTheBeafHandler{// 吃牛肉public void doIt() {System.out.println("eat with hand");}}// 吃牛肉方法:刀叉吃法
class KnifeFolkEatTheBeafHandler extends BaseEatTheBeafHandler{// 吃牛肉方法public void doIt() {System.out.println("eat with knife & folk");}}// 做法:火锅牛肉
class HotPotCookBeafHandler extends BaseCookBeafHandler {public void doId() {System.out.println("hot pot beaf");}
}// 吃牛肉方法:用筷子吃
class EatWithChopsticksHandler extends BaseEatTheBeafHandler {public void doId() {System.out.println("eat with chopsticks");}
}// 做法:加辣椒的做法
class ChilliCookBeafHandler extends BaseCookBeafHandler {public void doId() {super.doIt();System.out.println("add plenty of chilli");}}// 吃牛肉方法:说完再吃
class TalkAndEatTheBeafHandler extends BaseEatTheBeafHandler {public void doId() {System.out.print("いただきます");super.doIt();}
}// 做法:加咸鱼做法
class SaltFishCookBeafHandler extends BaseCookBeafHandler {public void doId() {super.doIt();System.out.println("add salt fish");}}

上述代码中,有两项重要调整:一是将模板中的两个重要节点“做牛肉”和“吃牛肉”方法拆出来,建立专门接口和实现,这样做的好处是这些功能点完全独立,可复用性更好,并且作为独立完整的小模块功能可以更强大和稳定;二是不再出现超多层继承,服务的每个实现都很清晰做了哪些事。相对来说,虽然比之前的实现多了很多Handler,并且没有做到“只要能复用就复用”,但层次会清晰很多,扩展和管理更方便。

现在如果北海道札幌市鬼子村的松尾桑想要吃牛肉,那么只需要添加一个SongWeiEat继承AbsEatAgain,并实现继承BaseCookBeafHandler和BaseEatTheBeafHandler的Handler就可以了。

这篇关于JavaWeb可扩展性之模板方法的运用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/654982

相关文章

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断

java如何解压zip压缩包

《java如何解压zip压缩包》:本文主要介绍java如何解压zip压缩包问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解压zip压缩包实例代码结果如下总结java解压zip压缩包坐在旁边的小伙伴问我怎么用 java 将服务器上的压缩文件解压出来,

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Spring WebFlux 与 WebClient 使用指南及最佳实践

《SpringWebFlux与WebClient使用指南及最佳实践》WebClient是SpringWebFlux模块提供的非阻塞、响应式HTTP客户端,基于ProjectReactor实现,... 目录Spring WebFlux 与 WebClient 使用指南1. WebClient 概述2. 核心依

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2