沉淀、分享、成长,让自己和他人都能有所收获!
代码一把梭,兄弟来背锅。
大部分做开发的小伙伴初心都希望把代码写好,除了把编程当作工作以外他们还是具备工匠精神的从业者。但很多时候又很难让你把初心坚持下去,就像;接了个烂手的项目、产品功能要的急、个人能力不足,等等原因导致工程代码臃肿不堪,线上频出事故,最终离职走人。
看了很多书、学了很多知识,多线程能玩出花,可最后我还是写不好代码!
这就有点像家里装修完了买物件,我几十万的实木沙发,怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足,也不一定是产品要得急 怎么实现我不管明天上线
。而很多时候是我们对编码的经验的不足和对架构的把控能力不到位,我相信产品的第一个需求往往都不复杂,甚至所见所得。但如果你不考虑后续的是否会拓展,将来会在哪些模块继续添加功能,那么后续的代码就会随着你种下的第一颗恶性的种子开始蔓延。
学习设计模式的心得有哪些,怎么学才会用!
设计模式书籍,有点像考驾驶证的科一、家里装修时的手册、或者单身狗的恋爱宝典。但!你只要不实操,一定能搞的乱码
七糟。因为这些指导思想都是从实际经验中提炼的,没有经过提炼的小白,很难驾驭这样的知识。所以在学习的过程中首先要有案例,之后再结合案例与自己实际的业务,尝试重构改造,慢慢体会其中的感受,从而也就学会了如果搭建出优秀的代码。
bugstack虫洞栈
,回复源码下载
获取工程 | 描述 |
---|---|
itstack-demo-design-2-00 | 场景模拟工程,模拟出使用Redis升级为集群时类改造 |
itstack-demo-design-2-01 | 使用一坨代码实现业务需求,也是对ifelse的使用 |
itstack-demo-design-2-02 | 通过设计模式优化改造代码,产生对比性从而学习 |
抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
可能在平常的业务开发中很少关注这样的设计模式或者类似的代码结构,但是这种场景确一直在我们身边,例如;
不同系统内的回车换行
\n
;\n\r
;IDEA 开发工具的差异展示(Win\Mac)
除了这样显而易见的例子外,我们的业务开发中时常也会遇到类似的问题,需要兼容做处理但大部分经验不足的开发人员,常常直接通过添加ifelse
方式进行处理了。
很多时候初期业务的蛮荒发展,也会牵动着研发对系统的建设。
预估QPS较低
、系统压力较小
、并发访问不大
、近一年没有大动作
等等,在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对 Redis
的使用,往往可能只要是单机的就可以满足现状。
不吹牛的讲百度首页我上学时候一天就能写完,等毕业工作了就算给我一年都完成不了!
但随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis
已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。
随着这次的升级,可以预见的问题会有;
itstack-demo-design-2-00
└── src
└── main
└── java
└── org.itstack.demo.design
├── matter
│ ├── EGM.java
│ └── IIR.java
└── RedisUtils.java
工程中的所有代码可以通过关注公众号:bugstack虫洞栈
,回复源码下载
进行获取。
综上可以看到,我们目前的系统中已经在大量的使用redis服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。
以下是案例模拟中原有的单集群Redis使用方式,后续会通过对这里的代码进行改造。
public interface CacheService {
String get(final String key);
void set(String key, String value);
void set(String key, String value, long timeout, TimeUnit timeUnit);
void del(String key);
}
public class CacheServiceImpl implements CacheService {
private RedisUtils redisUtils = new RedisUtils();
public String get(String key) {
return redisUtils.get(key);
}
public void set(String key, String value) {
redisUtils.set(key, value);
}
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
redisUtils.set(key, value, timeout, timeUnit);
}
public void del(String key) {
redisUtils.del(key);
}
}
讲道理没有ifelse解决不了的逻辑,不行就在加一行!
此时的实现方式并不会修改类结构图,也就是与上面给出的类层级关系一致。通过在接口中添加类型字段区分当前使用的是哪个集群,来作为使用的判断。可以说目前的方式非常难用,其他使用方改动颇多,这里只是做为例子。
itstack-demo-design-2-01
└── src
└── main
└── java
└── org.itstack.demo.design
├── impl
│ └── CacheServiceImpl.java
└── CacheService.java
CacheServiceImpl
中实现。public class CacheServiceImpl implements CacheService {
private RedisUtils redisUtils = new RedisUtils();
private EGM egm = new EGM();
private IIR iir = new IIR();
public String get(String key, int redisType) {
if (1 == redisType) {
return egm.gain(key);
}
if (2 == redisType) {
return iir.get(key);
}
return redisUtils.get(key);
}
public void set(String key, String value, int redisType) {
if (1 == redisType) {
egm.set(key, value);
return;
}
if (2 == redisType) {
iir.set(key, value);
return;
}
redisUtils.set(key, value);
}
//... 同类不做太多展示,可以下载源码进行参考
}
接下来我们通过junit单元测试的方式验证接口服务,强调日常编写好单测可以更好的提高系统的健壮度。
编写测试类:
@Test
public void test_CacheService() {
CacheService cacheService = new CacheServiceImpl();
cacheService.set("user_name_01", "小傅哥", 1);
String val01 = cacheService.get("user_name_01",1);
System.out.println(val01);
}
结果:
22:26:24.591 [main] INFO org.itstack.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:小傅哥
22:26:24.593 [main] INFO org.itstack.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:小傅哥
Process finished with exit code 0
接下来使用抽象工厂模式来进行代码优化,也算是一次很小的重构。
这里的抽象工厂的创建和获取方式,会采用代理类的方式进行实现。所被代理的类就是目前的Redis操作方法类,让这个类在不需要任何修改下,就可以实现调用集群A和集群B的数据服务。
并且这里还有一点非常重要,由于集群A和集群B在部分方法提供上是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建把不同的服务抽象为统一的接口做相同的业务。这一块与我们上一章节中的工厂方法模型
类型,可以翻阅参考。
itstack-demo-design-2-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── factory
│ │ ├── impl
│ │ │ ├── EGMCacheAdapter.java
│ │ │ └── IIRCacheAdapter.java
│ │ ├── ICacheAdapter.java
│ │ ├── JDKInvocationHandler.java
│ │ └── JDKProxy.java
│ ├── impl
│ │ └── CacheServiceImpl.java
│ └── CacheService.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
抽象工厂模型结构
ICacheAdapter
,定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapter
、IIRCacheAdapter
JDKProxy
、JDKInvocationHandler
,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。好,那么接下来会分别讲解几个类的具体实现。
public interface ICacheAdapter {
String get(String key);
void set(String key, String value);
void set(String key, String value, long timeout, TimeUnit timeUnit);
void del(String key);
}
EGMCacheAdapter
public class EGMCacheAdapter implements ICacheAdapter {
private EGM egm = new EGM();
public String get(String key) {
return egm.gain(key);
}
public void set(String key, String value) {
egm.set(key, value);
}
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
egm.setEx(key, value, timeout, timeUnit);
}
public void del(String key) {
egm.delete(key);
}
}
IIRCacheAdapter
public class IIRCacheAdapter implements ICacheAdapter {
private IIR iir = new IIR();
public String get(String key) {
return iir.get(key);
}
public void set(String key, String value) {
iir.set(key, value);
}
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
iir.setExpire(key, value, timeout, timeUnit);
}
public void del(String key) {
iir.del(key);
}
}
JDKProxy
public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = interfaceClass.getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
}
JDKInvocationHandler
public class JDKInvocationHandler implements InvocationHandler {
private ICacheAdapter cacheAdapter;
public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
this.cacheAdapter = cacheAdapter;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
}
}
invoke
中通过使用获取方法名称反射方式,调用对应的方法功能,也就简化了整体的使用。编写测试类:
@Test
public void test_CacheService() throws Exception {
CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
proxy_EGM.set("user_name_01","小傅哥");
String val01 = proxy_EGM.get("user_name_01");
System.out.println(val01);
CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
proxy_IIR.set("user_name_01","小傅哥");
String val02 = proxy_IIR.get("user_name_01");
System.out.println(val02);
}
JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
结果:
23:07:06.953 [main] INFO org.itstack.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:小傅哥
23:07:06.956 [main] INFO org.itstack.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:小傅哥
23:07:06.957 [main] INFO org.itstack.demo.design.matter.IIR - IIR写入数据 key:user_name_01 val:小傅哥
23:07:06.957 [main] INFO org.itstack.demo.design.matter.IIR - IIR获取数据 key:user_name_01
测试结果:小傅哥
Process finished with exit code 0
你的代码只是被ifelse埋上了!
当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层级结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度。重学 Java 设计模式:实战工厂方法模式
Java开发架构篇:初识领域驱动设计DDD落地
Java开发架构篇:DDD模型领域层决策规则树服务设计
Java开发架构篇:领域驱动设计架构基于SpringCloud搭建微服务
11 万字的字节码编程系列合集放送
CodeGuide | 程序员编码指南 Go!
本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!