设计一个扩展自抽象类geometricobject_JAVA程序员日常,记一次“设计过度”

ec977cdff7e4065ba5e4d2a7b5655d5b.png

一、故事的开始

最近开始一个新项目,项目面向用户比较小众,用户在在小程序上购买定制商品,基于这个项目我们可能会设计一些定制模板。比如有商品A是一瓶威士忌,用户可以定制这个商品的瓶标,上传一些自定义图片或则输入个性文字。用户自定义的内容不是随便用户选择,需要一个模板,那商品A下可能有5个模板,每个模板有不同内容,比如模板A可以上传一张图片显示在模板中间,而模板B的图片显示在右上角,而且模板B还可以输入自定义文字在模板下面。

本来这个需求是在前端根据用户的操作生成图片后上传到对象存储,然后运营人员就可以根据用户自定义的图片开始制作商品。但是产品和厂家沟通后需要一张高清大图供厂家参考制作,所以我们觉得把生成定制图片这个动作拿到后端来做。

基本思路其实比较简单,流程大致如下:

1.用户选择商品,然后在该商品可用模板中选择一个模板进行定制

2.用户根据模板标准可输入个性文字或则上传自定义图片到对象存储

3.前端提交用户自定义数据和用户选择的模板编码到后端

4.后端根据模板编码获取用户选择的模板

5.后端下载模板底图(模板底图事先已经保存在对象存储上),然后解析用户的自定义数据,如果有图片就到对象存储下载用户自定义图片

6.渲染自定义数据到底图上,最后上传完成的图片到对象存储

思路比较清晰,流程也很简单。这里的问题是每个模板自定义数据的内容不同,每个模板自定义数据在底图上的定位也不同,所以势必要对每个模板进行不同的处理。

二、第一次尝试

这个方案就比较简单,思路流程按照上面的步骤来,但是渲染的时候根据用户选择的模板写switch语句进行不同的操作。这个我就不说了,比较简单,但是缺点很明显,模板越多switch语句越复杂,维护难度越大。

三、第二次尝试

这个方案原则就是一个,后期添加模板的时候可直接添加类,而不是改动现有代码。引用P3C的一句话:

在极端情况下,交付的代码是不修改的,同一业务域内的需求变化,应通过模块或类的扩展来实现。

所以我想到使用service locator设计模式,为每一个模板创建一个类(称之为模板处理器),在服务启动的时候扫描指定包下的模板处理器,把这些处理器放入map容器中,key为模板编号,value为模板处理器实例。处理的时候直接从map里面获取对应处理器执行操作,这一步就去掉了繁杂的switch语句,要新增模板的时候直接添加类到指定包名下面即可在服务启动的时候扫描进去,这里就保证了这个功能遵循了P3C的原则。

上面全是思路,下面我开始一步步贴出代码,以下项目为示例项目,三方jar包有spingboot,hutool,fastjson和lombok。

1. 项目结构

首先看项目文件目录我把实现类都写了标注,接口类都能对应上就不说了,简单说一下核心类的作用:

  • TemplateService 这个类就是相当于调度器,处理器放置的map容器在这个类里面,项目启动的时候扫包注册模板处理器也在这个类
  • TemplateOssService 这个类是做对象存储的上传下载服务,比较简单不多说
  • AbstractTemplateHandlerService 这个抽象类的作用是 各个模板肯定有重复的操作,这里提取重复操作,具体处理器可以继承这个类实现代码复用,也就是模板设计模式的思想。

301853c27660d2fc9f4910d9f947634a.png
项目整体结构

2. 对象存储上传下载服务服务

这个服务的接口和实现是一个简单示例,代码如下

a6f88b8f80bb9d3fbb1940e274f32d21.png
对象存储服务接口

19401056af3552bc78639e39c1d5d51b.png
对象存储服务实现

3. 模板处理器服务

刚刚说了这里用了模板设计的思想,首先有一个接口,接口有两个方法,第一个方法调用处理器进行处理好说,第二个方法是用来初始化处理器的,因为处理器用我们自己的容器保留了实例,就不交由spring容器管理,但是处理器有需要spring容器里面的bean实例,所以这里使用这个方法来初始化处理器,让处理器在初始化的时候获取spring容器里面的bean实例。

e8ee7140ab3ec99c8185057701883776.png
处理器接口

接下来是模板设计模式中的“模板”,处理了每个模板处理器的都需要的上传下载服务,由于上传下载需要对象存储服务,所以使用init方法获取对象存储服务,最后继续用一个抽象方法initd,让子类也能获取spring容器里面的bean。

7619c3550d5f5830cec359773fcaf368.png
处理器抽象类

83a5360b9a71a408ea36c5de4e506068.png
处理器抽象类抽象方法

最后看一个测试模板,继承抽象类,重写必要的方法即可,在hand里面针对当前模板的规范处理图片并返回,这里要注意的是我使用了个自定义注解来标识这个处理器对应的模板便编码。

d98458bbd07abfff94eaca482e3a4f4d.png
自定义注解

0cbc1494540f35a696ff7f3e15b42137.png
模板处理器,对应编码“0001”

4. Template服务

这个服务刚也说了是一个调度器,项目启动的时候扫包注册模板处理器,处理的时候从map中获取对应的模板处理进行处理,接口就一个方法执行处理。

64aecb295c0fc0a0e1bc8fc36275c074.png
服务接口

实现类我们看两个地方,项目启动的时候调用这个方法进行扫包,这里我用了Hutool的工具简化代码,通过自定义注解获取处理器对应的编码作为key,然后实例化对象作为value放入map容器,代码倒数第二行是初始化容器,传入spring上下文。第二个地方是hand方法比较简介获取对应处理器执行后返回。

d4708e57d2ad8214861b8c523f355344.png
注册处理器到map容器

16cca45e3ad3fc6c00e253750e5b16a8.png
调用执行方法

到这里就完成了整个模块的设计,我们只要让客户端注入TemplateService执行方法hand方法即可,下面代码是调用demo。

7cae5cc806bce8afb5338f67dc420653.png
调用demo

四、进一步思考

到这里其实功能完成了,目标也达成了。但是我进一步思考到,既然我们有spring容器,为什么要自己来实例化处理器呢,而且自己实例化处理器还要我们来处理对象的生命周期,这里我只是简单了用到了init方法,假如我们的处理器需求比较复杂,要用到犹如spring bean一样多的生命周期,那就是徒增麻烦(当然就这个需求来说不太可能需要这么复杂的生命周期)。所以我想到把处理器交由spring来管理,那么如何将模板处理器对应的模板编码和spring容器里面的bean对应上呢,我想到spring容器可以通过名字来获取bean,那名字的规则其实就是类名首字母小写,所以是可以关联的,想到这里我准备对这个模板进行升级改造。

1. 去掉了init方法

首先我去掉了init方法 简化模板处理器接口和抽象类,然后在自定义注解里面添加一个方法获取当前处理器对应的spring bean名字,如果处理器自定了bean名字填入这个才能正确获取到spring容器里面的实例,没有自定义的就按照首字母小写的规则命名。

cc07bd6f22cff4b501e37385ec3c6964.png
简化的接口

e806bf175aa1290dc2dccca5ccf3d32b.png
简化的抽象类

8b9d11d3db6487057032ef97438ec795.png
添加service方法的自定义注解

4e0b3bf08eee749fde381a39eaa48940.png
简化后的处理器,交给spring管理不要忘记加@service注解

2. 改造一下TemplateService

还是两个主要地方,第一个启动注册的时候map容器的value不在放入处理器实例,而是放入处理器对应在spring容器里面的名字。第二获取获取处理器的时候先从map获取实例名字然后从spring上下文查找bean实列,找不到要抛出异常。

41fdcf8a04204d433fe58ffedc256b99.png
注册处理器

d12169ee8e60de141c9600b71bfd109f.png
获取处理器执行方法

最后调用demo和上面的一样,我们来看输出

2da0f01b22fb6161f3f7ebdc549437b2.png

四、结语

其实方案一和二大家都能想到,方案一我是无论如何不会使用的,所以我在这里也只是提了一下,没有贴代码,因为我根本没有这么写过。方案二这个设计早有人在网上讨论过,我也实际这么操作过的,但是最后方案三也是我这次接这个需求的时候才思考出来的。

其实就这个需求来说还有一个比较好的办法就是把每个模板需要的数据格式和图片定位等数据放在一个配置文件里面或者数据库,然后在根据用户选择的模板编码去获取对应的配置数据进行渲染。所以我说的这个需求案例不一定适合这套设计,但是我们日常工作绝对有这种场景,一方面我分享出来大家讨论借鉴,另一方面也是我的思考笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值