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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�