一篇文章带你轻松掌握四种代码包装技巧,快速重构你的编程实践
sdk对外暴露的接口不合理
我们经常使用一些sdk来完成我们的需求,但往往有些sdk对外暴露的接口并不合理,再加上如果这个sdk本身处于快速迭代期,每次变更某些api的话,业务方如果使用的地方较多,那么批量修改 其实也是比较麻烦的。
看看如何针对上述情况做一些改进
看一段示例代码:
public class SchoolSystemSdk {
/**
* 存储学生
*
* @param name 姓名
* @param age 年纪
* @param No 学号
*/
public void saveStudent(String name, int age, int No) {
}
/**
* 存储教师
*
* @param name 教师姓名
* @param studentNumber 学生数量
* @param schoolName 学校名称
*/
public void saveTeacher(String name, int studentNumber, String schoolName) {
}
}
public class TestMain {
public static void main(String[] args) {
SchoolSystemSdk schoolSystemSdk=new SchoolSystemSdk();
schoolSystemSdk.saveStudent("wuyue",18,12345);
schoolSystemSdk.saveTeacher("wuyanzu",18,"南京大学");
}
}
问题就是这个sdk对外提供的函数参数过多,没有封装性,且可以很容易想到日后这个sdk如果新增参数的话,我们的业务代码肯定要批量修改调用的地方,很是蛋疼。但是我们又没有权限修改sdk的源码(暂不考虑asm等字节码技术)
下面进行修改: 首先定义一下学生和老师
class Student {
String name;
int age;
int No;
}
class Teacher {
String name;
int studentNumber;
String schoolName;
}
再定义一个接口 ,注意接口的参数
public interface ISchoolSystem {
void saveStudent(Student student);
void saveTeacher(Teacher teacher);
}
然后定义我们的主类 注意类的声明
//extends了sdk的类 且实现了接口
public class SchoolSystemWrapper extends SchoolSystemSdk implements ISchoolSystem {
@Override
public void saveStudent(Student student) {
saveStudent(student.name, student.age, student.No);
}
@Override
public void saveTeacher(Teacher teacher) {
//懒癌发作 就此省略
}
}
最后调用
public class TestMain {
public static void main(String[] args) {
ISchoolSystem iSchoolSystem = new SchoolSystemWrapper();
//懒癌发作 这边构造函数 就没有传参了,大家自行领会意图
iSchoolSystem.saveStudent(new Student());
iSchoolSystem.saveTeacher(new Teacher());
}
}
很简单的几步 就可以把我们的痛点全部解决了。 即解决了原生sdk对外暴露函数参数不好的问题,也可以解决日后sdk更新时 多处修改的问题,日后有修改也只要在wrapper那里 改一处即可
功能类似的第三方sdk 统一管理
这个场景在业务中也不少见,比如某些需求 我们需要使用不同的第三方sdk 做处理,我们希望收拢这些不同第三方sdk的接口,然后对外暴露出一个简单的接口。
举例说明: 一段文本,我们需要过滤掉黄色信息,少儿不宜的信息,以及不符合政策法规的信息。 让我们看看这段代码应该如何写:
//过滤掉不良的黄色信息
public class AWordsFilter {
public String filterSexyWords(String text) {
return "";
}
}
//过滤掉政治信息
class BWordsFilter {
public String filterPoliticalWords(String text) {
return "";
}
}
//过滤掉工信部给的敏感信息 senseWords 为敏感词
class CWordsFilter {
public String filterSenselWords(String text, String senseWords) {
return "";
}
}
使用他
public class TestMain {
public static void main(String[] args) {
//原始文本
String text = "1231231asdasdasdasda";
//三种过滤规则
AWordsFilter aWordsFilter = new AWordsFilter();
BWordsFilter bWordsFilter = new BWordsFilter();
CWordsFilter cWordsFilter = new CWordsFilter();
//过滤成最终想要的信息
String t1 = aWordsFilter.filterSexyWords(text);
String t2 = bWordsFilter.filterPoliticalWords(t1);
String t3 = cWordsFilter.filterSenselWords(t2, "蛤蛤");
}
}
开始优化代码 首先定义2个接口:
public interface IWordsFilter {
String filter(String text);
}
public interface IWordsSenseFilter extends IWordsFilter {
String filter(String text, String sense);
}
然后来实现他们:
public class AWordsFilterAdaptor implements IWordsFilter {
AWordsFilter aWordsFilter;
@Override
public String filter(String text) {
return aWordsFilter.filterSexyWords(text);
}
}
class BWordsFilterAdaptor implements IWordsFilter {
BWordsFilter bWordsFilter;
@Override
public String filter(String text) {
return bWordsFilter.filterPoliticalWords(text);
}
}
class CWordsFilterAdaptor implements IWordsSenseFilter {
CWordsFilter cWordsFilter;
@Override
public String filter(String text, String sense) {
return cWordsFilter.filterSenselWords(text, sense);
}
@Override
public String filter(String text) {
return null;
}
}
最后统一管理
class FilterManager {
List<IWordsFilter> filters = new ArrayList<>();
public void addFilterType(IWordsFilter iWordsFilter) {
filters.add(iWordsFilter);
}
public String filterAll(String text, String sense) {
String maskText = text;
for (IWordsFilter iWordsFilter : filters) {
if (iWordsFilter instanceof IWordsSenseFilter) {
maskText = ((IWordsSenseFilter) iWordsFilter).filter(maskText, sense);
} else {
maskText = iWordsFilter.filter(text);
}
}
return maskText;
}
}
看下使用效果,是不是可以达到收拢的效果
String text = "1231231asdasdasdasda";
FilterManager filterManager=new FilterManager();
filterManager.addFilterType(new AWordsFilterAdaptor());
filterManager.addFilterType(new BWordsFilterAdaptor());
filterManager.addFilterType(new CWordsFilterAdaptor());
filterManager.filterAll(text,"蛤蛤");
到这里应该可以理解,这种adapter模式的写法,就是特别适合如下场景: 一种事后的补救策略,你无法控制sdk的代码质量,或者说虽然不是sdk代码,但是老代码坑比较多,一时半会不好大改,但是可以通过这种adapter的方式来达到修正你业务代码质量的目标
非功能性需求如何与业务代码做分离
平时开发中我们应该经常碰到类似的需求,比如说做了一些需求,但是要针对这些需求做监控,日志,埋点,统计,鉴权等等,这些东西的开发本质上和我们的业务开发 是分离的,不应该将他们的代码写在一起。那么如何做分离呢? 这里举一个例子
假设我们这里有一个用户的登录注册功能
public class UserController {
public void login() {
}
public void register() {
}
}
现在要求对这个登录注册做一些监控,比如记录一下登录和注册行为发起的时间,记录下用户的id等等。 直接将这些代码写到我们本身的登录注册业务中 是极其不优雅 也是不好维护的。
如果我们懒一点,不想修改我们的UserController源码 那就用extends的方式来做
class UserControllerProxy extends UserController
{
@Override
public void login() {
//在这里做一些监控的操作 代码省略
super.login();
}
@Override
public void register() {
//在这里做一些监控的操作 代码省略
super.register();
}
}
勤快一点 ,可以定义接口来做
public class UserController implements IUserCon {
public void login() {
}
public void register() {
}
}
interface IUserCon {
void login();
void register();
}
//数据统计
class DataStatic {
public void doSome() {
}
}
class UserControllerProxy implements IUserCon {
public UserControllerProxy(IUserCon iUserCon) {
this.iUserCon = iUserCon;
}
private IUserCon iUserCon;
private DataStatic dataStatic = new DataStatic();
@Override
public void login() {
//在这里做一些监控的操作 代码省略
dataStatic.doSome();
iUserCon.login();
}
@Override
public void register() {
//在这里做一些监控的操作 代码省略
dataStatic.doSome();
iUserCon.register();
}
}
到这里还没完噢,还有一种情况,就是如果说被我们代理的类页面方法很多,我们如果用上述的方法 来做proxy就真的很愚蠢了,因为你有大量的重复代码 要写在不同的函数中。 又是复制粘贴的做法了。
另外你想,如果说你这种埋点或者是监控类的需求比较多,那被代理的类 就很多了,不止登录注册啊,还有其他模块都要使用,你是不是要手动添加更多的Proxy类?万一哪天这些埋点的方法有变动,想想要改多少地方?
要解决这个问题 也很简单
动态代理 动态生成我们的类,动态的修改Proxy中的代码 即可
public class UserDynamicProxy implements InvocationHandler {
DataStatic dataStatic;
public UserDynamicProxy(Object target) {
this.target = target;
dataStatic = new DataStatic();
}
Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
dataStatic.doSome();
Object result = method.invoke(target, args);
return result;
}
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
使用起来也比较简单
UserDynamicProxy dynamicProxy = new UserDynamicProxy(new UserController());
IUserCon iUserCon = dynamicProxy.getProxy();
iUserCon.register();
如果你在项目中恰好遇到这种需求,可以多用用动态代理,很强的功能,极简的写法。原理在本篇就不做过多介绍了, 不是本文的重点。有兴趣的可以参见动态代理原理
最后强调一下: 代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、埋点、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让业务开发的同学只关注业务,不需要关注基础功能。 在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能
java IO 中的设计哲学
java io可能是java 体系里面最复杂的一个模块了,经常用,但是很少有人能讲清楚为啥这么设计,今天我们就扒了它的皮,看看能从中学习到什么
首先看一张老图, 应该很多人都很熟悉了
我们现在尝试读取一个文件,为了提高效率我们使用缓存buffer
InputStream in=new FileInputStream("F:\\Java huashan.pdf");
InputStream bin=new BufferedInputStream(in);
大家有没有发现 这样写有什么异样?为啥一定要用一个inputStream去初始化一个bufferins呢,为啥fileins可以直接传个path就创建,bufferins就不行?
bufferins的构造函数:
看看fileins的构造函数
所以这里问题就来了,为什么bufferins这个类的构造函数是ins作为参数,而不能直接设置成像fileins一样, 直接接受一个path作为参数?
我们直接这样写
InputStream in=new BufferedFileInputStream("F:\\Java huashan.pdf");
不好吗?
我们再回头看看之前的ins的子类 他是不止一个fileins子类的,他还有其他多个子类。如果我们单独在fileins后面派生一个bufferfins的子类,那么其他子类要使用这个功能 也得派生出对应的bufferins子类。这样就显的非常冗余了。最终的结果就是子类爆炸
所以在java的世界里面是有一个基本设计原则的: 组合大于集成
所以在java io世界里 很多类都不是直接派生自InputStrem 而是持有ins的一个实例
就像一个Wrapper 包裹住他们一样。
细心的人会发现 Bufferins与DataIns 都不是直接extends的ins啊 而是extends的
这又是什么设计?
众所周知的是我们的ins 是一个抽象类
public abstract class InputStream implements Closeable {
所以为了让我们增强功能的io类,比如bufferins datainputs 只实现一些他们增强功能的方法,哪些没有变动的方法自然就放到filterins里面默认实现他了。
这里还会有人问 既然inputstream是一个抽象类,那你说的默认实现为啥不在inputSteream里面写? 为啥要画蛇添足加一个filterins来写? 我们看个图
我们可以想一下,如果我们将默认的实现写在InputStream中,去掉这个filterIns层,
new Bufferins(new FileInput("path"))
这个时候我们Buffins 的那些ins默认的方法 我们必须要重写一遍 形如:
InputStrean ins;
public void f(){
ins.f()
}
因为如果我们不这么写,那我们bufferins的默认f方法走的就是ins中的默认实现 而不是我们传进来的FileInput实现了。
想清楚这点 我们就可以猜到FilterInput到底是什么作用了
所以你看
FilterInput这个类 帮我们解决的 不就是上述的痛点吗?可以避免我们再手动复制粘贴一次哪些默认的方法调用而已。
分析到这里我们就来总结一下java io世界中 所采用的装饰者wrapper写法 主要用来解决什么问题。
主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这是判断是否该用装饰器模式的一个重要的依据,他跟Proxy的写法是不同的**,Proxy主要是新增跟原来功能不相干的功能,而装饰者是增强原来的功能,或者说是增加类似的功能,但是带有优化的效果**。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
上一篇: 寻找法老雕鸮
推荐阅读
-
一篇文章带你轻松掌握四种代码包装技巧,快速重构你的编程实践
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾