当前位置:网站首页>一个快速切换一个底层实现的思路分享
一个快速切换一个底层实现的思路分享
2022-06-26 13:44:00 【李自提】
优质资源分享
| 学习路线指引(点击解锁) | 知识定位 | 人群定位 |
|---|---|---|
| 🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
| Python量化交易实战 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
| 目录 |
现实场景往往是这样,我们应对一个需求,很快就会有一个处理方法了,然后根据需求做了一个还不错的实现。因为实现了功能,业务很happy,老板很开心,all the world is beatiful.
但随着公司的发展,有人实现了一套底层的标准组件,按要求你必须要接入他那个,他的功能与你类似,但你必须要切换成那个。且不论其实现的质量怎么样,但他肯定是有一些优势的,不过他作为标准套件,不可能完全同你的需求一致。因此,这必定涉及到改造的问题。
一般这种情况下,我们是不太愿意接的,毕竟代码跑得好好的,谁愿意动呢?而且别人的实现如何,还没有经过考验,冒然接入,可能带来比较大的锅呢。(从0到1没人关注准确性,但从1到到1.1就会有人关注准确性了,换句话说这叫兼容性)
但是,往往迫于压力,我们又不得不接。
这时候我们有两种做法,一种是硬着头皮直接改代码为别人的方式。这种处理简单粗暴,而且没有后顾之忧。不过,随之而来的,就是大面积的回归测试,以及一些可能测试不到的点,意味着代码的回滚。对于一些线上运维比较方便的地方,也许我们是可以这样干。但这并不是本文推荐的做法,也不做更多讨论。
更稳妥的做法,应该是在保有现有实现的情况下,进行新实现的接入,至少你还可以对照嘛。进可攻,退可守。
返回顶部### 1. 快速接入新实现1:抽象类
既然我们不敢直接替换现有的实现,那么就得保留两种实现,所以可以用抽象类的方式,保持原有实现的同时,切入新的实现。是个比较直观的想法了,具体实现如下:
1. 抽象一个公共类出来
public abstract class AbstractRedisOperate {
private AbstractRedisOperate impl;
public AbstractRedisOperate() {
String strategy = "a"; // from config
if("a".equals(strategy)) {
impl = new RedisOperateA1Imp();
}
else {
impl = new RedisOperateB2Imp();
}
}
// 示例操作接口
public void set(String key, String value);
}
- 实现两个具体类
// 实现1,完全依赖于抽象类实现(旧有功能)
public class RedisOperateOldImp extends AbstractRedisOperate {
}
// 实现2,新接入的实现
public class RedisOperateB2Imp extends AbstractRedisOperate {
@Override
public void set(String key, String value) {
System.out.println("this is b's implement...");
}
}
- 保持原有的实现类入口,将其实现变成一个外观类或者叫适配器类
// 加载入口
@Service
public class RedisOperateFacade extends AbstractRedisOperate {
public RedisOperateFacade() {
// case1. 直接交由父类处理
super();
}
@Override
public void set(String key, String value) {
// fake impl
}
}
以上实现有什么好处呢?首先,现有的实现被抽离,且不用做改动被保留了下来。新的实现类自行实现一个新的。通过一个公共的切换开关,进行切换处理。这样一来,既可以保证接入了新实现,而且也保留了旧实现,在出未知故障时,可以回切实现。
以上实现有什么问题?
当我们运行上面的代码时,发现报错了,为什么?因为出现了死循环。虽然我们只加载了一个 Facade 的实现,但是在调用super时,super会反过来加载具体的实现,具体的实现又会去加载抽象类super,如此循环往复,直到栈溢出。也叫出现了死循环。
返回顶部### 2. 解决简单抽象带来的问题
上一节我们已经知道为什么出现加载失败的问题,其实就是一个循环依赖问题。如何解决呢?
其实就是简单地移动下代码,不要将判断放在默认构造器中,由具体的外观类进行处理,加载策略由外观类决定,而非具体的实现类或抽象类。
具体操作如下:
// 1. 外观类控制加载
@Service
public class RedisOperateFacade extends AbstractRedisOperate {
public RedisOperateFacade() {
// case1. 直接交由父类处理
// super();
// case2. 决定加载哪个实现
String strategy = "a"; // from config center
if("a".equals(strategy)) {
setImpl(new RedisOperateOldImp());
}
else {
setImpl(new RedisOperateB2Imp());
}
}
}
// 2. 各实现保持自身不动
public class RedisOperateOldImp extends AbstractRedisOperate {
// old impl...
}
public class RedisOperateB2Imp extends AbstractRedisOperate {
// new impl...
@Override
public void set(String key, String value) {
System.out.println("this is b's implement...");
}
}
// 3. 抽象类不再进行加载策略处理
public abstract class AbstractRedisOperate {
// 持有具体实现
private AbstractRedisOperate impl;
public AbstractRedisOperate() {
}
protected void setImpl(AbstractRedisOperate impl) {
this.impl = impl;
}
// 示例操作接口, old impl...
public abstract void set(String key, String value);
}
做了微小的改动,将加载策略从抽象类中转移到外观类中,就可以达到正确的加载效果了。实际上,为了简单起见,我们甚至可以将原有的实现全部copy到抽象类中,而新增的一个原有实现类,则什么也不用做,只需新增一个空继承抽象类即可。而新的实现,则完全覆盖现有的具体实现就可以了。从而达到一个最小的改动,而且顺利接入一个新实现的效果。
但是如果依赖于抽象类的具体实现的话,会带来一个问题,那就是如果我们的子类实现得不完善,比如遗漏了一些实现时,代码本身并不会报错提示。这就给我们带来了潜在的风险,因为那样就会变成,一部分是旧有实现,另一部分是新的实现。这可能会有两个问题:一是两个实现有一个报错一个正常;二是无法正常切换回滚,两种实现耦合在了一起。
返回顶部### 3. 更完善的方案:基于接口的不同实现
怎么办呢?我们可以再抽象一层接口出来,各实现针对接口处理,只有外观类继承了抽象类,而且抽象类同时也实现了接口定义。这样的话,就保证了各实现的完整性,以及外观类的统一性了。这里,我利用的是语法的强制特性,即接口必须得到实现的语义,进行代码准确性的保证。(当然了,所有的现实场景,接口都必须有相应的实现,因为外部可见只有接口,如果不实现则必定不合法)
具体实现如下:
//1. 统一接口定义
public interface UnifiedRedisOperate {
void set(String key, String value, int ttl);
// more interface definitions...
}
// 2. 各子实现类
public class RedisOperateOldImp implements UnifiedRedisOperate {
@Override
public void set(String key, String value) {
System.out.println("this is a's implement...");
}
}
public class RedisOperateB2Imp implements UnifiedRedisOperate {
@Override
public void set(String key, String value) {
System.out.println("this is b's implement...");
}
}
// 3. 外观类的实现
@Service
public class RedisOperateFacade extends AbstractRedisOperate {
public RedisOperateFacade() {
// case1. 直接交由父类处理
// super();
// case2. 外观类控制加载
String strategy = "a"; // from config center
if("a".equals(strategy)) {
setImpl(new RedisOperateOldImp());
}
else {
setImpl(new RedisOperateB2Imp());
}
}
}
public abstract class AbstractRedisOperate implements UnifiedRedisOperate {
private UnifiedRedisOperate impl;
protected void setImpl(UnifiedRedisOperate impl) {
this.impl = impl;
}
// 接口委托
public void set(String key, String value) {
impl.set(key, value);
}
// more delegates...
}
看起来是多增加了一个接口类,但是实际上整个代码更加清晰易读了。实际上,一个好的设计,最初应该也是基于接口的(即面向接口编程),而我们在这里重新抽象出一个接口类来,实际上就是弥补之前设计的不足,也算是一种重构了。所有的实现都基于接口,一个实现都不能少,从而减少了出错的概率。
如此,我们就可以放心的进行生产切换了。
文章原创发布微信公众号地址: 一个快速切换一个底层实现的思路分享
边栏推荐
- Linear basis
- H5关闭当前页面,包括微信浏览器(附源码)
- Common evaluation indexes of classification model -- confusion matrix and ROC curve
- GDAL grid data types and their type codes
- Introduction to basic knowledge of C language (Daquan) [suggestions collection]
- 2021-10-29 atcoder ABC157——B - Bingo
- DOS command
- Equation derivation: second order active bandpass filter design! (download: Tutorial + schematic + Video + code)
- 方程推导:二阶有源带通滤波器设计!(下载:教程+原理图+视频+代码)
- Gartner 2022 Top Strategic Technology Trends Report
猜你喜欢

From Celsius to the three arrows: encrypting the domino of the ten billion giants, and drying up the epic liquidity

布局管理器~登录界面的搭建实例

Eigen(3):error: ‘Eigen’ has not been declared

How to convert data in cell cell into data in matrix

Leaflet load day map

永远不要使用Redis过期监听实现定时任务!

ThreadLocal giant pit! Memory leaks are just Pediatrics

Server create virtual environment run code

Build your own PE manually from winpe of ADK

9 regulations and 6 prohibitions! The Ministry of education and the emergency management department jointly issued the nine provisions on fire safety management of off campus training institutions
随机推荐
Usage of unique function
Pycharm远程连接服务器来跑代码
Correlation analysis related knowledge
备战数学建模32-相关性分析2
Electron
从Celsius到三箭:加密百亿巨头们的多米诺,史诗级流动性的枯竭
C language | file operation and error prone points
Assert and constd13
ThreadLocal giant pit! Memory leaks are just Pediatrics
Eigen(3):error: ‘Eigen’ has not been declared
wptx64能卸载吗_win10自带的软件哪些可以卸载
Gartner 2022 Top Strategic Technology Trends Report
Understand the difference and use between jsonarray and jsonobject
9项规定6个严禁!教育部、应急管理部联合印发《校外培训机构消防安全管理九项规定》
How to check if a text field is empty or not in swift
MySQL | basic commands
Combat readiness mathematical modeling 31 data interpolation and curve fitting 3
SwiftUI找回丢失的列表视图(List)动画
"Scoi2016" delicious problem solution
Record: why is there no lightning 4 interface graphics card docking station and mobile hard disk?