背景:
入行写java也快3年了,从一开始不懂什么事设计模式,死记硬背设计模式的概念,到后来随着写代码的深入,慢慢的开始理解设计模式的强大,就像古装片武功的心法一样,需要自身去主动地理解去体会,才能化为己用,在编码层面在上升一个阶段。
最近我带着几个同事做了一个媒体运营推广的项目,一开始的业务设计是我们开发一个产品对应一个唯一的H5页面,用户访问H5页面后填写一个信息,然后提交到后台,我们公司再和第三方公司合作,把这部分填写的信息抽取有价值的地方对接第三方公司API传递过去。这种业务过程其实很简单,用最常用的MVC架构就可以完美解决,其实我们平时常用的controller->service->serviceImpl这个过程其实就是代理模式的体现,只不过我们的service接口下只有一个对应的实现类。但是后台业务有所发展,公司又对接了其他公司的API,要求我们把这个产品的数据也传给新对接的公司,这个时候正常业务场景下我们把新的公司接入到serviceImpl里就行了,于是我把这个工作交给一个4年经验的同事去做,我相信他应该可以做的不错,但是事与愿违,他做的并不好(这是后话了,后台我自己给重构了这部分代码)。但是过两天我就想到,这么做其实是有问题的,因为一开始的设置是一对一(一个产品对应一个推送公司),到现在发展到一对多(一个产品对应多个推送公司),问题在哪呢?实际上当业务人员开始要求产品接入多家公司的时候这是个显性需求,隐性需求其实很多,就算业务人员现在不提出来,以后也会提出来,如果设计这部分代码现在不设计好,那么以后也会重构,隐性需求有那些呢?
业务上:
- 推送公司的状态问题:既然一个产品会合作多家公司,以后就肯定会不和哪家公司合作,对这个产品下的某家公司做无效,这样数据也不会继续推送给他家
- 推送公司优先级问题:合作一段时间后通过统计就能发现哪家公司的转化率好,哪家公司的利润高,这样对于产品下的合作公司势必会产生一个推送优先级的问题,比如满足所有推送公司推送条件优先推给谁等等
编码上
- 代码耦合问题:由于一个serviceImpl中承担了多家合作公司推送方法,虽然可以通过if/else的方式来对推送优先及做限定,但是没办法对合作公司的状态做调整,就算勉强做了调整,这样也会导致这个类的代码过分冗余,难以维护,难以看懂,也不符合设计的单一原则。正规的做法应该是针对每一个合作公司的API对接写一个实现类,然后让这些实现类统一实现一个合作公司的接口,只需要重写推送方法和规则要求验证就行。然后设计一个推送公司的表来统一维护这些推送公司实现类,让调用方通过工厂模式来动态选择应该推给谁
- 后期维护问题:上面也说了,那样写相当于写死,不可能业务人员换一个优先级,禁用部分合作公司就改一次代码,上一次线,这样代价太大,也不合适
- 分工合作问题:如果把所有逻辑写在一个实现类里,万一以后短期内接入多家公司API的话,怎么才能做到及时有效的迭代?全写在一个类里,意味着只能由一个程序员去编码,这样多家API就相当于同步阻塞,写完一个在写一个,可能有人会说可以把多家API分给多个程序员啊,可是你逻辑实现写在一个类里分开后是不是还得合并代码,合并冲突?所以,通过工厂模式,各自写各自的实现类,只需要统一实现一个接口,重写1,2个方法,这样对于主类完全没有干扰,可以愉快的分工合作
废话了不少,简单看看代码实现吧
- 先看看统一管理的表结构:
Field | Type | Comment |
---|---|---|
id | int(10) unsigned NOT NULL | 主键 |
product_id | int(10) unsigned NOT NULL | 产品编号 |
name | varchar(100) NOT NULL | 合作方通道名称 |
clazz | varchar(500) NOT NULL | 服务 |
lvl | int(10) unsigned NOT NULL | 排序 |
status | int(10) unsigned NOT NULL | 状态 |
rmk | varchar(500) NULL | 备注 |
create_time | datetime NULL | 创建时间 |
//接口
public interface PushCompanyChannelService {
boolean pushData(Data data);
}
//实现类A公司
@Service
@Slf4j
public class ACompanyChannel implements PushCompanyChannelService {
@Override
public boolean pushData(Data data) {
//具体细节。。。
}
}
//实现类B公司
@Service
@Slf4j
public class BCompanyChannel implements PushCompanyChannelService {
@Override
public boolean pushData(Data data) {
//具体细节。。。
}
}
//业务调用实现类
@Service
@Slf4j
public class ServiceImpl implements Service{
@Override
public boolean pushData2Channel(Data data) {
//根据产品id查询推送通道,按照设置等级排序
Sort sort = new Sort(Sort.Direction.ASC,"lvl");
//获取产品配置的有效通道
List<PushCompanyChannel> pushCompanyChannels = pushCompanyChannelRepository.findByProductIdIsAndStatusIs(data.getProductId(),1,sort);
for (PushCompanyChannel pushChannel : pushCompanyChannels){
//按照预先设置推送通道优先级动态代理推送数据
PushCompanyChannelService bean = (PushCompanyChannelService ) SpringContext.getBean(Class.forName(pushChannel.getClazz()));
boolean result = bean.pushData(data);
if (result){
//一比数据只能推送一次,一个通道推送成功即跳出
break;
}
}
}
}