从管道符到Java编程
说明:在linux操作系统中,管道符用竖线(|)表示,表示将前一个命令的输出作为后面命令的输入。通过这样“流式”地组合操作,能极大地扩展linux命令功能,处理一些复杂场景。
如下,grep命令,能查看某文件中的关键字,tail命令,能查看文件末尾,通过用管道符连接,能实现查看某关键字,在文件中最后一次出现的位置
这种设计思想,像流水线一样,从源头到出口,层层处理,最终达到目的,很有启发。在Java的设计模式中,责任链设计模式,就能体现这种思想。
责任链模式介绍,参考下面这篇文章:
- 【设计模式-4.3】行为型——责任链模式
下面我通过两个在实际工作中的场景,来介绍这种思想的体现
参数校验
一般来说,接口进入Service的第一步是参数校验,如下,
@Transactional(rollbackFor = Exception.class)
public List<UserDTO> insert1(UserDTO userDTO) {
log.info("进入service");
// 1.参数校验
userDTO.checkParams();
// 2.插入数据
asyncService.insertDTO1(userDTO);
// 3.返回查询
return userMapper.selectAll();
}
UserDTO对象里面,写校验方法,如下:
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import java.io.Serializable;
@Data
public class UserDTO implements Serializable {
private String id;
private String username;
private String password;
public void checkParams() {
if (StrUtil.isBlank(username)) {
throw new IllegalArgumentException("用户名不能为空");
}
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("密码不能为空");
}
if (username.length() > 25) {
throw new IllegalArgumentException("用户名长度不能超过25个字符");
}
if (!validatePassword(password)) {
throw new IllegalArgumentException("密码需要包含数字、大小写字符和特殊字符");
}
}
/**
* 校验密码是否符合要求
* @param password 密码
* @return true符合,false不符合
*/
private boolean validatePassword(String password) {
// 至少包含一个大写字母
boolean hasUpperCase = password.matches("(?=.*[A-Z]).*");
// 至少包含一个小写字母
boolean hasLowerCase = password.matches("(?=.*[a-z]).*");
// 至少包含一个数字
boolean hasDigit = password.matches("(?=.*\\d).*");
// 至少包含一个特殊字符
boolean hasSpecialChar = password.matches("(?=.*[!@#$%^&*()\\-_+=\\[\\]{}|;':\",.<>/?]).*");
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
}
}
参考管道符思想,可以设计成如下,将对属性的校验抽出来,解耦,并返回当前对象;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import java.io.Serializable;
@Data
public class UserDTO implements Serializable {
private String id;
private String username;
private String password;
public UserDTO checkUsername() {
if (StrUtil.isBlank(username)) {
throw new IllegalArgumentException("用户名不能为空");
}
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("密码不能为空");
}
return this;
}
public UserDTO checkPassword() {
if (username.length() > 25) {
throw new IllegalArgumentException("用户名长度不能超过25个字符");
}
if (!validatePassword(password)) {
throw new IllegalArgumentException("密码需要包含数字、大小写字符和特殊字符");
}
return this;
}
/**
* 校验密码是否符合要求
* @param password 密码
* @return true符合,false不符合
*/
private boolean validatePassword(String password) {
// 至少包含一个大写字母
boolean hasUpperCase = password.matches("(?=.*[A-Z]).*");
// 至少包含一个小写字母
boolean hasLowerCase = password.matches("(?=.*[a-z]).*");
// 至少包含一个数字
boolean hasDigit = password.matches("(?=.*\\d).*");
// 至少包含一个特殊字符
boolean hasSpecialChar = password.matches("(?=.*[!@#$%^&*()\\-_+=\\[\\]{}|;':\",.<>/?]).*");
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
}
}
使用的时候,需要校验什么字段就接什么方法
@Transactional(rollbackFor = Exception.class)
public List<UserDTO> insert1(UserDTO userDTO) {
log.info("进入service");
// 1.参数校验
userDTO.checkUsername().checkPassword();
// 2.插入数据
asyncService.insertDTO1(userDTO);
// 3.返回查询
return userMapper.selectAll();
}
流式组装数据
有个场景,在一个社交系统中,每个用户对象,集成了许多的数据,如
- 基础信息(头像、昵称、ID、个性签名)
- 好友数据(好友的基础信息、好友量、权限配置、分组)
- 系统设置(消息设置、隐私设置、通用)
这些都隶属于一个用户,现在需要获取某个用户的所有信息,通常的做法是分别取查对应的表,然后组装。这里可以使用一种高级的方式,如下:
先写一个接口,定义一个获取用户数据的方法
import org.example.pojo.UserData;
/**
* 获取用户数据接口
*/
public interface UserDataService {
/**
* 获取用户数据
* @param userData 用户数据
* @return 用户数据对象
*/
UserData getUserData(UserData userData);
}
UserData类如下:
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* 用户数据
*/
@Data
public class UserData implements Serializable {
private String id;
/**
* 用户数据
*/
private Map<String, String> data;
}
接着,写三个实现类,分别实现获取基础信息、好友数据、系统设置的方法,如下:
(获取用户基础信息)
import org.example.pojo.UserData;
import org.example.service.UserDataService;
import org.springframework.stereotype.Service;
/**
* 获取用户基础信息
*/
@Service
public class BaseUserDataService implements UserDataService {
@Override
public UserData getUserData(UserData userData) {
userData.getData().put("基础信息1", "头像");
userData.getData().put("基础信息2", "昵称");
userData.getData().put("基础信息3", "ID");
userData.getData().put("基础信息4", "个性签名");
return userData;
}
}
(获取用户好友数据)
import org.example.pojo.UserData;
import org.example.service.UserDataService;
import org.springframework.stereotype.Service;
/**
* 获取用户好友数据
*/
@Service
public class FriendUserDataService implements UserDataService {
@Override
public UserData getUserData(UserData userData) {
userData.getData().put("好友数据1", "好友的基础信息");
userData.getData().put("好友数据2", "好友量");
userData.getData().put("好友数据3", "权限配置");
userData.getData().put("好友数据4", "分组");
return userData;
}
}
(获取用户设置数据)
import org.example.pojo.UserData;
import org.example.service.UserDataService;
import org.springframework.stereotype.Service;
/**
* 获取用户设置数据
*/
@Service
public class SettingUserDataService implements UserDataService {
@Override
public UserData getUserData(UserData userData) {
userData.getData().put("系统设置1", "消息设置");
userData.getData().put("系统设置2", "隐私设置");
userData.getData().put("系统设置3", "通用");
return userData;
}
}
使用起来就很方便了,如下:
import org.example.pojo.UserData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
@Service
public class UserServiceImpl {
/**
* 自动装配
* 把所有 UserDataService 类型的实现类都拿过来
*/
@Autowired
private List<UserDataService> userDataServiceList;
public UserData getUserData() {
UserData userData = new UserData();
userData.setId("123456");
userData.setData(new HashMap<>());
// 调用实现类的方法,去拿对应的数据
for (UserDataService userDataService : userDataServiceList) {
userDataService.getUserData(userData);
}
return userData;
}
}
调用接口,查看结果,如下:
以上方式,利用了Spring自动装配和Java多态(调用接口的方法,实际走的是子类实现的方法)的特性,非常优雅地实现了数据组装。架构搭好,现在只需要针对不同的数据,去实现具体的逻辑即可。个人觉得这种场景在实际业务中应该是比较常见的。
总结
本文从linux管道符的设计思想出发,介绍了这种思想在Java编程上的应用,希望能对大家有启发。