详解Java实践之抽象工厂模式


Posted in Java/Android onJune 18, 2021
目录
  • 一、前言
  • 二、开发环境
  • 三、抽象工厂模式介绍
  • 四、案例场景模拟
    • 4.1、场景模拟工程
    • 4.2、场景简述
      • 4.2.1、模拟单机服务 RedisUtils
      • 4.2.2、模拟集群 EGM
      • 4.2.3、模拟集群 IIR
    • 4.3、单集群代码使用
      • 4.3.1、定义使用接口
      • 4.3.2、实现调用代码
  • 五、代码实现
    • 5.1、工程结构
    • 5.2、ifelse实现需求
    • 5.3、测试验证
  • 六、抽象工厂模式重构代码
    • 6.1、工程结构
    • 6.2、代码实现
      • 6.2.1、定义适配接口
      • 6.2.2、实现集群使用服务
      • 6.2.3、定义抽象工程代理类和实现
    • 6.3、测试验证
  • 七、总结

 

一、前言

代码一把梭,兄弟来背锅。

大部分做开发的小伙伴初心都希望把代码写好,除了把编程当作工作以外他们还是具备工匠精神的从业者。但很多时候又很难让你把初心坚持下去,就像;接了个烂手的项目、产品功能要的急、个人能力不足,等等原因导致工程代码臃肿不堪,线上频出事故,最终离职走人。

看了很多书、学了很多知识,多线程能玩出花,可最后我还是写不好代码!

这就有点像家里装修完了买物件,我几十万的实木沙发,怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足,也不一定是产品要得急 怎么实现我不管明天上线。而很多时候是我们对编码的经验的不足和对架构的把控能力不到位,我相信产品的第一个需求往往都不复杂,甚至所见所得。但如果你不考虑后续的是否会拓展,将来会在哪些模块继续添加功能,那么后续的代码就会随着你种下的第一颗恶性的种子开始蔓延。

学习设计模式的心得有哪些,怎么学才会用!

设计模式书籍,有点像考驾驶证的科一、家里装修时的手册、或者单身狗的恋爱宝典。但!你只要不实操,一定能搞的乱七糟。因为这些指导思想都是从实际经验中提炼的,没有经过提炼的小白,很难驾驭这样的知识。所以在学习的过程中首先要有案例,之后再结合案例与自己实际的业务,尝试重构改造,慢慢体会其中的感受,从而也就学会了如果搭建出优秀的代码。

 

二、开发环境

JDK 1.8

Idea + Maven

工程 描述
itstack-demo-design-2-00 场景模拟工程,模拟出使用Redis升级为集群时类改造
itstack-demo-design-2-01 使用一坨代码实现业务需求,也是对ifelse的使用
itstack-demo-design-2-02 通过设计模式优化改造代码,产生对比性从而学习

 

三、抽象工厂模式介绍

详解Java实践之抽象工厂模式

抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。

可能在平常的业务开发中很少关注这样的设计模式或者类似的代码结构,但是这种场景确一直在我们身边,例如;

1.不同系统内的回车换行

  • Unix系统里,每行结尾只有 <换行>,即 \n
  • Windows系统里面,每行结尾是 <换行><回车>,即 \n\r
  • Mac系统里,每行结尾是 <回车>

2.IDEA 开发工具的差异展示(Win\Mac)

详解Java实践之抽象工厂模式

除了这样显而易见的例子外,我们的业务开发中时常也会遇到类似的问题,需要兼容做处理但大部分经验不足的开发人员,常常直接通过添加ifelse方式进行处理了。

 

四、案例场景模拟

详解Java实践之抽象工厂模式

很多时候初期业务的蛮荒发展,也会牵动着研发对系统的建设。

预估QPS较低系统压力较小并发访问不大近一年没有大动作等等,在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对 Redis 的使用,往往可能只要是单机的就可以满足现状。

不吹牛的讲百度首页我上学时候一天就能写完,等毕业工作了就算给我一年都完成不了!

但随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis 已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。

随着这次的升级,可以预见的问题会有;

  • 很多服务用到了Redis需要一起升级到集群。
  • 需要兼容集群A和集群B,便于后续的灾备。
  • 两套集群提供的接口和方法各有差异,需要做适配。
  • 不能影响到目前正常运行的系统。

 

4.1、场景模拟工程

itstack-demo-design-2-00

└── src

    └── main

        └── java

            └── org.itstack.demo.design

                ├── matter

                │   ├── EGM.java

                │   └── IIR.java

                └── RedisUtils.java

 

4.2、场景简述

 

4.2.1、模拟单机服务 RedisUtils

详解Java实践之抽象工厂模式

  • 模拟Redis功能,也就是假定目前所有的系统都在使用的服务
  • 类和方法名次都固定写死到各个业务系统中,改动略微麻烦

 

4.2.2、模拟集群 EGM

详解Java实践之抽象工厂模式

模拟一个集群服务,但是方法名与各业务系统中使用的方法名不同。有点像你mac,我用win。做一样的事,但有不同的操作。

 

4.2.3、模拟集群 IIR

详解Java实践之抽象工厂模式

这是另外一套集群服务,有时候在企业开发中就很有可能出现两套服务,这里我们也是为了做模拟案例,所以添加两套实现同样功能的不同服务,来学习抽象工厂模式。

综上可以看到,我们目前的系统中已经在大量的使用redis服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。

 

4.3、单集群代码使用

以下是案例模拟中原有的单集群Redis使用方式,后续会通过对这里的代码进行改造。

详解Java实践之抽象工厂模式

 

4.3.1、定义使用接口

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);

}

 

4.3.2、实现调用代码

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解决不了的逻辑,不行就在加一行!

此时的实现方式并不会修改类结构图,也就是与上面给出的类层级关系一致。通过在接口中添加类型字段区分当前使用的是哪个集群,来作为使用的判断。可以说目前的方式非常难用,其他使用方改动颇多,这里只是做为例子。

 

5.1、工程结构

itstack-demo-design-2-01

└── src

    └── main

        └── java

            └── org.itstack.demo.design

                ├── impl

                │   └── CacheServiceImpl.java

                └── CacheService.java

此时的只有两个类,类结构非常简单。而我们需要的补充扩展功能也只是在 CacheServiceImpl 中实现。

 

5.2、ifelse实现需求

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);
    }


}
  • 这里的实现过程非常简单,主要根据类型判断是哪个Redis集群。
  • 虽然实现是简单了,但是对使用者来说就麻烦了,并且也很难应对后期的拓展和不停的维护。

 

5.3、测试验证

接下来我们通过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在部分方法提供上是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建把不同的服务抽象为统一的接口做相同的业务。这一块与我们上一章节中的工厂方法模型类型,可以翻阅参考。

 

6.1、工程结构

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

抽象工厂模型结构

详解Java实践之抽象工厂模式

工程中涉及的部分核心功能代码,如下;

  • ICacheAdapter,定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapterIIRCacheAdapter
  • JDKProxyJDKInvocationHandler,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

好,那么接下来会分别讲解几个类的具体实现。

 

6.2、代码实现

 

6.2.1、定义适配接口

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);

}

这个类的主要作用是让所有集群的提供方,能在统一的方法名称下进行操作。也方面后续的拓展。

 

6.2.2、实现集群使用服务

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);
    }

}

以上两个实现都非常容易,在统一方法名下进行包装。

 

6.2.3、定义抽象工程代理类和实现

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中通过使用获取方法名称反射方式,调用对应的方法功能,也就简化了整体的使用。
  • 到这我们就已经将整体的功能实现完成了,关于抽象工厂这部分也可以使用非代理的方式进行实现。

 

6.3、测试验证

编写测试类:

@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

运行结果正常,这样的代码满足了这次拓展的需求,同时你的技术能力也给老板留下了深刻的印象。研发自我能力的提升远不是外接的压力就是编写一坨坨代码的接口,如果你已经熟练了很多技能,那么可以在即使紧急的情况下,也能做出完善的方案。

 

七、总结

抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。

你的代码只是被ifelse埋上了!当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层级结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度。

那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。

以上就是详解Java实践之抽象工厂模式的详细内容,更多关于Java抽象工厂模式的资料请关注三水点靠木其它相关文章!

Java/Android 相关文章推荐
教你用Java Swing实现自助取款机系统
Jun 11 Java/Android
源码解读Spring-Integration执行过程
Jun 11 Java/Android
Spring Cache和EhCache实现缓存管理方式
Jun 15 Java/Android
ConstraintValidator类如何实现自定义注解校验前端传参
Jun 18 Java/Android
Java基于字符界面的简易收银台
Jun 26 Java/Android
Java中PriorityQueue实现最小堆和最大堆的用法
Jun 27 Java/Android
Java常用函数式接口总结
Jun 29 Java/Android
java设计模式--建造者模式详解
Jul 21 Java/Android
JVM钩子函数的使用场景详解
Aug 23 Java/Android
详解Spring Security中的HttpBasic登录验证模式
Mar 17 Java/Android
Java Spring Boot 正确读取配置文件中的属性的值
Apr 20 Java/Android
Java服务调用RestTemplate与HttpClient的使用详解
Jun 21 Java/Android
Java框架入门之简单介绍SpringBoot框架
详解Java实践之建造者模式
新手入门Jvm-- JVM对象创建与内存分配机制
springboot利用redis、Redisson处理并发问题的操作
ConstraintValidator类如何实现自定义注解校验前端传参
SpringBoot项目中控制台日志的保存配置操作
Jun 18 #Java/Android
浅谈@Value和@Bean的执行顺序问题
Jun 16 #Java/Android
You might like
PHP bin2hex()函数基础实例讲解
2019/02/11 PHP
laravel 5.3 单用户登录简单实现方法
2019/10/14 PHP
javascript获取当前ip的代码
2009/05/10 Javascript
js 获取后台的字段 改变 checkbox的被选中的状态 代码
2013/06/05 Javascript
基于jQuery的JavaScript模版引擎JsRender使用指南
2014/12/29 Javascript
深入理解JavaScript系列(49):Function模式(上篇)
2015/03/04 Javascript
uploadify多文件上传参数设置技巧
2015/11/16 Javascript
js表单验证实例讲解
2016/03/31 Javascript
Node.js的npm包管理器基础使用教程
2016/05/26 Javascript
js初始化验证实例详解
2016/11/26 Javascript
jQuery使用正则表达式替换dom元素标签用法示例
2017/01/16 Javascript
js实现图片旋转 js滚动鼠标中间对图片放大缩小
2017/07/05 Javascript
微信小程序使用video组件播放视频功能示例【附源码下载】
2017/12/08 Javascript
使用vue-router完成简单导航功能【推荐】
2018/06/28 Javascript
Angular动画实现的2种方式以及添加购物车动画实例代码
2018/08/09 Javascript
JavaScript常用数组操作方法,包含ES6方法
2020/05/10 Javascript
JS实现给数组对象排序的方法分析
2019/06/24 Javascript
vue中实现弹出层动画效果的示例代码
2020/09/25 Javascript
深入理解Python分布式爬虫原理
2017/11/23 Python
PyGame贪吃蛇的实现代码示例
2018/11/21 Python
在python3中pyqt5和mayavi不兼容问题的解决方法
2019/01/08 Python
Python模块的加载讲解
2019/01/15 Python
Python使用MyQR制作专属动态彩色二维码功能
2019/06/04 Python
用python按照图像灰度值统计并筛选图片的操作(PIL,shutil,os)
2020/06/04 Python
Python如何把字典写入到CSV文件的方法示例
2020/08/23 Python
喜诗官方在线巧克力店:See’s Candies
2017/01/01 全球购物
西班牙床垫网上商店:Colchones.es
2018/05/06 全球购物
Java中compareTo和compare的区别
2016/04/12 面试题
运动会通讯稿400字
2014/01/28 职场文书
电大会计学自我鉴定
2014/02/06 职场文书
市场营销战略计划书
2014/05/06 职场文书
自我推荐信范文
2014/05/09 职场文书
师德师风承诺书
2014/05/23 职场文书
2015年资料员工作总结
2015/04/25 职场文书
一行代码python实现文件共享服务器
2021/04/22 Python
python+pyhyper实现识别图片中的车牌号思路详解
2022/12/24 Python