我们先来看一张图片:
健身的朋友都知道哑铃,中间比较细,两头比较重。那么和我们编程有什么关系呢?先描述一个概念,我们大多数初中级开发人员,都喜欢直线式开发模式,也就是来了一个需求我就可以完全从头到脚写一套,接下来我们对这种方式存在的问题做详细分析以及给出一种比较好的解决方案。
一、线性编程以及存在的问题
所谓线性编程,就是采取简单粗暴的方式,对类似业务的需求都实现一套, 基本实现模式如下图:
存在即合理,对这种大多数人都喜欢使用的编码方式,肯定有其存在的意义,我们先分析下其优缺点:
优点
1.上手简单,对程序员能力要求无门槛
2.通俗易懂,从头到尾没有分支逻辑
缺点
1.存在很多逻辑相似的重复代码
2.如果业务发生变化,可能导致所有线路代码变更
3.对于一些非核心逻辑,参数校验、权限校验,每条链路都要写一份重复代码
二、“哑铃式”编程实现思想以及解决的问题
对于“哑铃式”编程,应该大部分人没有这个概念,或者说已经知道这种编程方式,但是对这个奇怪的命名不知所措。首先声明一下,“哑铃式”编程是我自己根据实际开发经验总结提炼后的一个命名。我们先上一张图,针对上一步改造后的图:
这幅图和前边那幅图不一样的地方是,取消了服务层,增加了调配层和执行器层,其核心思想有两点:
1)收口;所有的请求由调度层收拢,便于管控
2)抽象;把一些通用的逻辑抽象到调度层
3)差异性;不同的请求类型有通用的逻辑,但是也有差异性的逻辑
新加两层的作用:
I)调配层:逻辑收口,抽象通用逻辑,根据不同操作类型调度不同执行器。
II)执行器层:控制器层不直接和外部服务或者本地DB操作打交道,不同操作类型的参数和操作权限个性化校验。
前边分析了“哑铃式”编程的实现思想,我们来分析下其优缺点:
优点
1.代码结构化程度高
2.插件式编程
3.代码复用度高,冗余度低
缺点
1.门槛比较高;有大量编程经验基类的开发人员才会有这个意识和思想
2.代码结构和层次复杂度变高
三、“哑铃式”编程实例实现
上边我们分析了传统线性编程和“哑铃式”编程的优缺点,我们根据具体实例来实现“哑铃式”编程。假如我们有个用户的增删改查的需求,接下来我们编写代码实现:
上图是我们用到的类,其中比较核心的类是:
IOperation
/**
* 操作接口
*/
public interface IOperation {
/**
* 具体操作
*
* @param t
* @throws UserException
*/
void operate(T t) throws UserException;
}
AbstractUserOperation
/**
* 抽象实现
*/
public abstract class AbstractUserOperation implements IOperation {
@Autowired
protected UserDao userDao;
@Override
public void operate(UserOptContext context) throws UserException {
checkParameter(context);
if(hasPermission(context)) {
execute(context);
} else {
notPermissionExecute(context);
}
}
/**
* 校验操作权限和数据权限
*
* @param context
* @return
* @throws UserException
*/
public boolean hasPermission(UserOptContext context) throws UserException {
return true;
}
/**
* 具体执行操作,交给子类实现
*
* @param context
* @throws UserException
*/
public abstract void execute(UserOptContext context) throws UserException;
/**
* 没有权限执行的操作
*
* @param context
* @throws UserException
*/
public void notPermissionExecute(UserOptContext context) throws UserException {
throw new UserException("没有权限操作");
}
/**
* 参数校验,交给子类实现差异化
*
* @param context
* @throws UserException
*/
public abstract void checkParameter(UserOptContext context) throws UserException ;
}
InsertUserOperation
/**
* 新增
*
*/
@Component
public class InsertUserOperation extends AbstractUserOperation {
@Override
public void execute(UserOptContext context) throws UserException {
this.userDao.create(context.getName(),context.getSex(),context.getAge());
}
@Override
public void checkParameter(UserOptContext context) throws UserException {
ParameterChecker.checkNotNull(context,"context");
ParameterChecker.checkNotNull(context.getName(),"name");
ParameterChecker.checkNotNull(context.getAge(),"age");
}
}
DeleteUserOperation
/**
* 删除
*
*/
@Component
public class DeleteUserOperation extends AbstractUserOperation {
@Override
public void execute(UserOptContext context) throws UserException {
this.userDao.deleteByPk(context.getId());
}
@Override
public void checkParameter(UserOptContext context) throws UserException {
ParameterChecker.checkNotNull(context,"context");
ParameterChecker.checkNotNull(context.getId(),"id");
}
}
UpdateUserOperation
/**
* 更新
*
*/
@Component
public class UpdateUserOperation extends AbstractUserOperation {
@Override
public void execute(UserOptContext context) throws UserException {
this.userDao.updateByPk(context.getId(),context.getName(),context.getAge());
}
@Override
public void checkParameter(UserOptContext context) throws UserException {
ParameterChecker.checkNotNull(context,"context");
ParameterChecker.checkNotNull(context.getAge(),"id");
ParameterChecker.checkNotNull(context.getName(),"name");
ParameterChecker.checkNotNull(context.getAge(),"age");
}
}
调度器UserOptManager
/**
* 用户操作调配器
*/
public class UserOptManager {
private Map proccessMap = new HashMap();
public void operate(UserOptTypeEnum optTypeEnum,UserOptContext context) throws UserException {
IOperation operation = proccessMap.get(optTypeEnum);
if(null == operation) {
throw new UserException("执行器不存在");
}
context.setOperateType(optTypeEnum.getCode());
operation.operate(context);
}
/**
* 新增
*
* @param context
* @throws UserException
*/
public void insert(UserOptContext context) throws UserException {
this.operate(UserOptTypeEnum.INSERT,context);
}
/**
* 删除
*
* @param context
* @throws UserException
*/
public void delete(UserOptContext context) throws UserException {
this.operate(UserOptTypeEnum.DELETE,context);
}
/**
* 更新
* @param context
* @throws UserException
*/
public void update(UserOptContext context) throws UserException {
this.operate(UserOptTypeEnum.UPDATE,context);
}
public void setProccessMap(Map proccessMap) {
this.proccessMap = proccessMap;
}
}
还有bean配置文件
控制器层写了三个测试方法:
@RequestMapping("/user/insert")
public Map saveUser(HttpServletRequest request,
HttpServletResponse response,
@RequestParam("name") String name,
@RequestParam("sex") Integer sex,
@RequestParam("age") Integer age) {
Map result = new HashMap();
if(idParamIllegal(name,age,sex)) {
result.put("success",false);
result.put("msg","参数非法");
return result;
}
try {
UserOptContext context = new UserOptContext();
context.setName(name);
context.setAge(age);
context.setSex(sex);
this.userOptManager.insert(context);
result.put("success",true);
result.put("msg","新增成功");
return result;
} catch (Exception e) {
e.printStackTrace();
result.put("success",false);
result.put("msg","新增操作异常");
}
return result;
}
@RequestMapping("/user/delete")
public Map deleteUser(HttpServletRequest request,
HttpServletResponse response,
@RequestParam("id") Long id) {
Map result = new HashMap();
if(idParamIllegal(id)) {
result.put("success",false);
result.put("msg","参数非法");
return result;
}
try {
UserOptContext context = new UserOptContext();
context.setId(id);
this.userOptManager.delete(context);
result.put("success",true);
result.put("msg","删除成功");
return result;
} catch (Exception e) {
e.printStackTrace();
result.put("success",false);
result.put("msg","删除操作异常");
}
return result;
}
@RequestMapping("/user/update")
public Map updateUser(HttpServletRequest request,
HttpServletResponse response,
@RequestParam("id") Long id,
@RequestParam("name") String name,
@RequestParam("sex") Integer sex,
@RequestParam("age") Integer age
) {
Map result = new HashMap();
if(idParamIllegal(id,name,age,sex)) {
result.put("success",false);
result.put("msg","参数非法");
return result;
}
try {
UserOptContext context = new UserOptContext();
context.setId(id);
context.setName(name);
context.setAge(age);
context.setSex(sex);
this.userOptManager.update(context);
result.put("success",true);
result.put("msg","更新成功");
return result;
} catch (Exception e) {
e.printStackTrace();
result.put("success",false);
result.put("msg","更新操作异常");
}
return result;
}
/**
* 检查参数是否非法
* @param params
* @return
*/
private boolean idParamIllegal(Object ... params) {
for(Object obj : params) {
if(null == obj) {
return true;
}
}
return false;
}
启动器:
@SpringBootApplication
@ImportResource("classpath:user-opt.xml")
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
}
项目启动后,对用户的增删改操作都没有问题,有兴趣的可以自己把代码拉下来跑一下。对于上面的代码实现,我整理了一张图:
从图中我们可以很清晰的看出,请求到UserOptManager被收住,然后到底层业务逻辑又扩散,也就是先粗后细,然后再变粗,特别像平时健身用的哑铃。
总结
通过上述一系列描述,我们对“哑铃式”编程做了很详细的介绍,并且用代码实现了这种编程模式,其实我想表达的核心思想是,在日常开发过程中要学会业务归类和逻辑抽象,该做成通用逻辑的时候就抽出来,该使用个性化逻辑的时候就表现出差异化,希望在日常开发和学习中对大家有帮助。
领取专属 10元无门槛券
私享最新 技术干货