我们经常使用一些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的接口,然后对外暴露出一个简单的接口。
举例说明: 一段文本,我们需要过滤掉黄色信息,少儿不宜的信息,以及不符合政策法规的信息。 让我们看看这段代码应该如何写:
//过滤掉不良的黄色信息 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 体系里面最复杂的一个模块了,经常用,但是很少有人能讲清楚为啥这么设计,今天我们就扒了它的皮,看看能从中学习到什么
首先看一张老图, 应该很多人都很熟悉了
我们现在尝试读取一个文件,为了提高效率我们使用缓存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主要是新增跟原来功能不相干的功能,而装饰者是增强原来的功能,或者说是增加类似的功能,但是带有优化的效果**。除此之外,装饰器模式还有一个特点,那就是可以对 原始类嵌套使用多个装饰器 。为了满足这个应用场景,在设计的时候, 装饰器类需要跟原始类继承相同的抽象类或者接口 。