工厂 + 策略 + 模板方法模式实际应用场景

问题场景:项目中需要实现PC端和APP端推广二维码的功能,其实采用一种简单的实现方式就是使用if-else判断是使用PC端还APP端的逻辑,这种做法虽然可以实现,但是如果再加上Mac端,H5端等等就需要在原代码上添加很多的if,代码看起来很不优雅而且修改了原代码,基于对修改关闭对扩展开放的原则,我使用了工厂模式 + 策略模式 + 模板方法模式来实现这一场景。

设备工厂类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DeviceFactory {

private static Map<String, AbstractDevice> strategyMap = Maps.newHashMap();

public static AbstractDevice getInvokeStrategy(String deviceName) {
return strategyMap.get(deviceName);
}
public static void register(String deviceName, AbstractDevice abstractDevice) {
if (StringUtils.isEmpty(deviceName) || null == abstractDevice) {
return;
}
strategyMap.put(deviceName, abstractDevice);
}
}

抽象设备类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class AbstractDevice implements InitializingBean {

private static Logger logger = LoggerFactory.getLogger(AppHandler.class);

private static final String URL = "https://openerp.banksteel.com/m/register.html";
private static final String MOBILE = "mobile";
private static final String INVITE_CONTENT_UP = "邀请码: ";
private static final String INVITE_CONTENT_DOWN = "邀请人: ";

@Autowired
private UserInfoService userInfoService;
@Autowired
private SysConfigService sysConfigService;

public InviteResultVO getQRCode(InviteUserVO inviteUserVO, String device) {
throw new UnsupportedOperationException();
}

/**
* @author lixiang
* @Description 根据手机号获取用户信息
* @Date 2021/3/2 18:46
**/
public List<UserInfo> getUserInfoByMobile(String mobile) {
SaasParameter.setMemberId("-1");
Map<String, Object> map = new HashMap<>();
map.put(MOBILE, mobile);
return userInfoService.queryByMap(map);
}

/**
* @author lixiang
* @Description 获取二维码和邀请链接并封装信息
* @Date 2021/3/2 18:46
**/
public InviteResultVO getURLAndAssembleInfo(Long userId, String userName, String device) {
byte[] logoFileByte;
try {
ClassPathResource classPathResource = new ClassPathResource("image/SaaS-logo.png");
logoFileByte = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
int length = logoFileByte.length;
logger.info("=================getInviteQR-errorlength:{}" + length);
} catch (Exception e) {
logger.error("================getInviteQR-error:{}", e);
throw new ParamsValidException(e);
}
String url = URL + "?inviteId=" + userId + "?device=" + device;
SysConfig envType = sysConfigService.findSysConfigByKey("environmentType");
String value ;
if (envType == null ) {
value = "";
} else {
if (!org.apache.commons.lang3.StringUtils.isBlank(envType.getValue()) && envType.getValue().length() > 2) {
value = envType.getValue().substring(0, 2);
} else {
value = "";
}
}
String qRBase64 = QrCodeUtils.generateQrCodeAsBase64(url, logoFileByte, " " + INVITE_CONTENT_UP + userId + value, " "+INVITE_CONTENT_DOWN + userName);
logger.error("================getInviteQR-qRBase64:{}", qRBase64);
InviteResultVO resultVO = new InviteResultVO();
resultVO.setInviteCode(qRBase64);
resultVO.setInviteUrl(url);
return resultVO;
}
}

APP设备类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class AppHandler extends AbstractDevice {

@Override
public InviteResultVO getQRCode(InviteUserVO inviteUserVO, String device) {
// 根据手机号获取用户信息
List<UserInfo> userInfos = super.getUserInfoByMobile(inviteUserVO.getMobile());
if (CollectionUtils.isEmpty(userInfos)) {
throw new ParamsValidException("获取用户信息异常!");
}
// 获取二维码和邀请链接并封装信息
return super.getURLAndAssembleInfo(userInfos.get(0).getId(), userInfos.get(0).getName(), device);
}

@Override
public void afterPropertiesSet() throws Exception {
DeviceFactory.register("app", this);
}
}

PC设备类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
public class PcHandler extends AbstractDevice{

@Autowired
private UserRegisterAO userRegisterAO;

@Override
public InviteResultVO getQRCode(InviteUserVO inviteUserVO, String device) {
// 根据手机号获取用户信息
List<UserInfo> userInfos = super.getUserInfoByMobile(inviteUserVO.getMobile());
Long userId;
String userName;
if (CollectionUtils.isEmpty(userInfos)) {
// 用户不存在,注册用户
userId = userRegisterAO.quickRegister(inviteUserVO);
userName = inviteUserVO.getUserName();
} else {
// 用户存在,直接校验
userRegisterAO.checkVerifyCode(inviteUserVO.getImgCheckCode(), inviteUserVO.getImgVerifyCode());
userRegisterAO.checkMobilCheckCode(inviteUserVO.getMobile(), inviteUserVO.getMobilCheckCode(), SmsConstants.QUICK_MOBILE_CHECKCODE_REGISTER);
userId = userInfos.get(0).getId();
userName = userInfos.get(0).getName();
}
// 获取二维码和邀请链接并封装信息
return super.getURLAndAssembleInfo(userId, userName, device);
}

@Override
public void afterPropertiesSet() throws Exception {
DeviceFactory.register("pc", this);
}
}

调用COntroller类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("/user/register")
@Api(tags = {"用户相关"})
public class UserRegisterController extends BaseController {

@ResponseBody
@RequestMapping(value = "/get/device/inviteQR", method = RequestMethod.POST)
@WriteBuzLog(buzModel = "用户管理", buzName = "获取二维码")
@ApiOperation(value = "获取PC端和APP端邀请二维码", httpMethod = "POST", consumes = "application/json;charset=UTF-8",notes = "commond命令:get_device_invite_QR")
@PermissionSource(command = "get_device_invite_QR", level=PermissionLevel.ALL)
public ResponseEntity<InviteResultVO> getDeviceInviteQR(@RequestBody @Valid InviteUserVO inviteUserVO, @ApiParam(name = "device", value = "PC端或者APP端",required = true) @RequestParam String device) {
AbstractDevice strategy = DeviceFactory.getInvokeStrategy(device);
return returnSuccess(200, "生成二维码成功", strategy.getQRCode(inviteUserVO, device));
}
}

总结:

以上实现方式优点是:如果需要添加一个Mac端只需要添加一个类去继承AbstractDevice就可以做到,无需改动原来的任何代码,符合开闭原则,也去除所有的if-else。
上述代码都是一些简单代码,我只是希望通过上面的例子告诉自己在写代码时不要只是单纯的只是想完成功能,而要更多的思考如何去设计代码,使代码更具可扩展性,写出更优雅的代码。