之前发现工厂模式能干掉代码里面大量的if else的混乱代码。我又在研究我的一坨代码的地方。发现有个场景适合桥接模式。其实重构自己的代码的点其实很简单,从你原来开发的一坨代码上好好研究下业务逻辑就会发现都是可以优化的地方。毕竟这些长业务的地方如果不优化,之后上新业务去改这些地方,测试你会发现要把老流程全走一遍会非常头疼。所以优化是势在必行的。
桥接模式的主要作用就是通过将抽象部分与实现部分分离,把多种可匹配的使用进行组合。说白了核心就是在A类中含有B类的接口,通过构造函数传递B类的实现,这个B类就是设计的桥。(来源于《重学Java设计模式》)
根据作者的例子他的场景里面支付方式和支付的验证有多种,且是组合的一种方式。刚好我这里有个开发场景也是这种组合式的方式。我现有业务是要给一个任务去分配可用的实例去执行。分配之前需要去注册中心去获取可以实例数里面剩余线程数量,如果有空余就分配。在早期版本没有在微服务里面实现时,所以服务是没有注册到注册中心,是下层服务主动请求管理服务,管理服务用redis去保存心跳。然后任务下发的时候去redis里面校验。后来服务升级为微服务后,服务全部注册到zookeeper上,校验的方式从redis获取替换为从zookeeper上获取。在没用设置模式时,我的改造是把原来redis实现部分删了,然后替换为zookeeper然后测试的时候怕出错,就把整个流程都测了,开发时间很大。如果使用桥接模式,把一开始的实例线程获取情况用接口是实现,在需求变更后,只需要写测试时测试通过zookeeper获取实例情况的代码即可,这即增加了代码的拓展性又缩短了测试时间。
--task
--- ManyPictureTaskService.java
--- OnePictureTaskService.java
--- PictureTaskService.java
--distribution
--- RedisPictureScoreDistribution.java
--- ZookeeperScoreDistribution.java
--- PictureScoreDistribution.java
-- PictureTaskFactory.java
代码目录可以看出来我这里有2个任务类型一个的单张图片获取,一个是多张图片获取。任务分配能力有redis校验方式,zookeeper校验方式。定义一个PictureTaskService抽象类,在里面引用PictureScoreDistribution接口。然后实现ManyPictureTaskService和OnePictureTaskService。最后结合工厂模式把2个实现类由PictureTaskFactory获取。当http请求过来的时候,会带上taskType,根据这个值就可以在factory里面获取到任务要调用的是哪个图片获取方法了。distribution里面的分配方法由于一个服务只有一个实现,所以在类上面结合Springboot的注解@ConditionalOnProperty来激活。这样在未来如果分配方法又有新需求变更了,我们只需要实现接口,然后在配置文件里面增加配置属性就可以直接切换分配的实现。如果切图方法有多张,单张增加新的需求,也只要实现切图任务接口,这样测试起来就不需要再去主流程里面测对单张切图,多张切图有没有影响了。
/**
* 切图任务业务
*/
public abstract class PictureTaskService {
public PictureTaskService(PictureScoreDistribution pictureScoreDistribution) {
this.pictureScoreDistribution = pictureScoreDistribution;
}
protected PictureScoreDistribution pictureScoreDistribution;
/**
* 创建切图任务
*
* @param body
* @return
*/
public abstract ResultData createTask(TaskRouteBody body);
}
/**
* 可用实例获取方法
*/
public interface PictureScoreDistribution {
JSONObject getServer();
}
/**
* 通过redis zset方式获取到可用的instance
*/
@Slf4j
@Component
@ConditionalOnProperty
public class RedisPictureScoreDistribution implements PictureScoreDistribution {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final Long HEADER_INDEX = 0L;
private static final Long FIRST_END_INDEX = -1L;
@Override
public JSONObject getServer() {
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(RegexConstants.REDIS_KEY_PICTURE_NODES, HEADER_INDEX, FIRST_END_INDEX);
if (typedTuples.isEmpty()) {
//没有活着的实例
throw new CoordinateException(ReturnCode.NO_INSTANCE_CAN_USE, ReturnCode.NO_INSTANCE_CAN_USE.getMsg());
} else {
int index = 0;
for (ZSetOperations.TypedTuple<Object> object : typedTuples) {
String instanceId = (String) object.getValue();
String appName = (String) redisTemplate.opsForHash().get(instanceId, "appName");
log.info("instanceId:{},appName:{}.", instanceId, appName);
index++;
//判断
String simpleInstance = stringRedisTemplate.opsForValue().get(RegexConstants.REDIS_KEY_HEART_HEADER + instanceId);
log.info("heart-key:{}.\nsimpleInstance:{}.", RegexConstants.REDIS_KEY_HEART_HEADER + instanceId, simpleInstance);
if (StringUtils.isEmpty(simpleInstance)) {
if (index == typedTuples.size()) {
//最后一个实例都无法拿来分配任务
throw new CoordinateException(ReturnCode.NO_INSTANCE_CAN_USE, ReturnCode.NO_INSTANCE_CAN_USE.getMsg());
} else {
//一个实例不存在,继续往下找
continue;
}
} else {
Double afterScore = object.getScore() - 1;
if (afterScore >= 0) {
JSONObject result = new JSONObject();
result.put("instanceId", instanceId);
result.put("appName", appName);
return result;
} else {
if (index == typedTuples.size()) {
//最后一个实例都无法拿来分配任务
throw new CoordinateException(ReturnCode.NO_INSTANCE_CAN_USE);
}
continue;
}
}
}
}
throw new CoordinateException(ReturnCode.NO_INSTANCE_CAN_USE, ReturnCode.NO_INSTANCE_CAN_USE.getMsg());
}
}
/**
* 单张切图调用
*/
@Slf4j
@Service("manyPictureTaskService")
public class ManyPictureTaskService extends PictureTaskService {
@Autowired
private TaskFeignImpl taskFeign;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public ManyPictureTaskService(PictureScoreDistribution pictureScoreDistribution) {
super(pictureScoreDistribution);
}
@Override
public ResultData createTask(TaskRouteBody body) {
JSONObject server = pictureScoreDistribution.getServer();
String instanceId = server.getString("instanceId");
long now = System.currentTimeMillis();
JSONObject task = JsonUtil.toJSONObject(body);
task.put("createTime", now);
task.put("updateTime", now);
task.put("instanceId", instanceId);
task.put("appName", server.getString("appName"));
task.put("status", body.getCommand());
String taskKey = RegexConstants.REDIS_KEY_TASK_KEY + instanceId;
String taskJson = task.toJSONString();
Task record = JSONObject.parseObject(taskJson, Task.class);
record.setStatus(TaskStatusEnums.RUN.val());
taskFeign.addTask(record);
redisTemplate.opsForList().rightPush(taskKey, taskJson);
redisTemplate.opsForZSet().incrementScore(RegexConstants.REDIS_KEY_PICTURE_NODES, instanceId, -1);
return ResultUtil.success();
}
}
/**
* 单张切图调用
*/
@Slf4j
@Service("onePictureTaskService")
public class OnePictureTaskService extends PictureTaskService {
@Autowired
public OnePictureTaskService(PictureScoreDistribution pictureScoreDistribution) {
super(pictureScoreDistribution);
}
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Value("${cutting.picture.one-cutting-url}")
private String oneCuttingUrl;
@Autowired
private RestTemplate restTemplate;
@Override
public ResultData createTask(TaskRouteBody body) {
JSONObject server = pictureScoreDistribution.getServer();
String instanceId = server.getString("instanceId");
redisTemplate.opsForZSet().incrementScore(RegexConstants.REDIS_KEY_PICTURE_NODES, instanceId, -1);
String instanceKey = String.format(RegexConstants.CUT_PICTURE_INSTANCE_INFO, instanceId);
ResultData resultData;
try {
String ipAddress = (String) redisTemplate.opsForHash().get(instanceKey, "ipAddr");
Integer port = (Integer) redisTemplate.opsForHash().get(instanceKey, "port");
String taskJson = JSONObject.toJSONString(body);
resultData = restTemplate.postForObject("http://" + ipAddress + ":" + port + oneCuttingUrl, taskJson, ResultData.class);
} catch (Exception e) {
log.error("cutting one picture fail!");
resultData = new ResultData();
resultData.setErrorCode("-1");
resultData.setErrorMsg("获取单张切图失败");
}
redisTemplate.opsForZSet().incrementScore(RegexConstants.REDIS_KEY_PICTURE_NODES, instanceId, 1);
return resultData;
}
}
@Component
public class PictureTaskFactory {
@Resource(name = "onePictureTaskService")
private PictureTaskService onePictureTaskService;
@Resource(name = "manyPictureTaskService")
private PictureTaskService manyPictureTaskService;
/**
* 获取切图方法
*
* @param taskType
* @return
*/
public PictureTaskService getPictureTaskService(Integer taskType) {
if (TaskTypeEnums.CUT_ONE_PICTURE.val() == taskType) {
return onePictureTaskService;
} else if (TaskTypeEnums.CUT_MANY_PICTURES.val() == taskType) {
return manyPictureTaskService;
} else {
throw new CoordinateException(ReturnCode.TASK_TYPE_IS_NO_EXIST);
}
}
}