说明:本文基于场景介绍抽象类在实际 Java 开发中的作用
写法
Java 中,抽象类用 abstract 关键字声明,抽象类中允许有非抽象方法,如下:
/**
* 登录接口抽象类
*/
public abstract class AbstractLoginService implements LoginService {
}
当一个类实现某接口,但没有实现接口内的所有抽象方法时,该类必须定义为抽象类。
背景
(1)登录接口
假设有一个登录接口,需要实现多种登录方式,有密码登录、微信扫码登录、手机短信登录,该接口需要根据传入的参数,调用不同的登录实现类。
如下:
import com.hezy.pojo.*;
import com.hezy.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 登录接口
*/
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private Map<String, LoginService> loginServiceMap;
@PostMapping
public String login(@RequestBody LoginUserDTO loginUserDTO) {
LoginUser loginUser = switch (loginUserDTO.getType()) {
case "password" -> LoginUserToPassword.mapToPasswordUser(loginUserDTO.getImageCode(), loginUserDTO.getLoginUserMap());
case "phone" -> LoginUserToPhone.mapToPhoneUser(loginUserDTO.getImageCode(), loginUserDTO.getLoginUserMap());
case "wechat" -> LoginUserToWechat.mapToWechatUser(loginUserDTO.getImageCode(), loginUserDTO.getLoginUserMap());
default -> throw new IllegalArgumentException("未知类型");
};
return loginServiceMap.get(loginUserDTO.getType()).login(loginUser);
}
}
其中,登录用户入参如下:
import lombok.Data;
import java.util.Map;
/**
* 登录接口入参
*/
@Data
public class LoginUserDTO {
/**
* 图形验证码
*/
private String imageCode;
/**
* 登录方式
*/
public String type;
/**
* 用户信息
*/
private Map<String, Object> loginUserMap;
}
接口内,会根据传递的 type 字段,分别实例化对应登录类型的用户对象(因为不同的登录方式,需要的登录参数不一样)
(登录用户父类)
import lombok.Data;
import java.io.Serializable;
/**
* 登录用户父类
*/
@Data
public class LoginUser implements Serializable {
/**
* 图形验证码
*/
private String imageCode;
}
(密码登录用户)
import lombok.Data;
import java.util.Map;
/**
* 密码登录用户
*/
@Data
public class LoginUserToPassword extends LoginUser {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 将map映射为LoginUserToPassword对象
*/
public static LoginUserToPassword mapToPasswordUser(String imageCode, Map<String, Object> loginUserMap) {
LoginUserToPassword user = new LoginUserToPassword();
user.setImageCode(imageCode);
user.setUsername((String) loginUserMap.get("username"));
user.setPassword((String) loginUserMap.get("password"));
return user;
}
}
(手机短信登录)
import lombok.Data;
import java.util.Map;
/**
* 手机短信登录用户
*/
@Data
public class LoginUserToPhone extends LoginUser {
/**
* 手机号
*/
private String phone;
/**
* 验证码
*/
private String code;
/**
* 将map映射为LoginUserToPassword对象
*/
public static LoginUser mapToPhoneUser(String imageCode, Map<String, Object> loginUserMap) {
LoginUserToPhone user = new LoginUserToPhone();
user.setImageCode(imageCode);
user.setPhone((String) loginUserMap.get("phone"));
user.setCode((String) loginUserMap.get("code"));
return user;
}
}
(微信扫码登录)
import lombok.Data;
import java.util.Map;
/**
* 微信扫码登录用户
*/
@Data
public class LoginUserToWechat extends LoginUser {
/**
* 微信code
*/
private String code;
/**
* 微信昵称
*/
private String nickname;
/**
* 将map映射为LoginUserToPassword对象
*/
public static LoginUser mapToWechatUser(String imageCode, Map<String, Object> loginUserMap) {
LoginUserToWechat user = new LoginUserToWechat();
user.setImageCode(imageCode);
user.setCode((String) loginUserMap.get("code"));
user.setNickname((String) loginUserMap.get("nickname"));
return user;
}
}
(2)登录实现
定义一个登录接口
import com.hezy.pojo.LoginUser;
/**
* 登录接口
*/
public interface LoginService {
/**
* 登录方法
*/
String login(LoginUser loginUser);
}
三种实现类
(密码登录)
import com.hezy.pojo.LoginUser;
import com.hezy.pojo.LoginUserToPassword;
import org.springframework.stereotype.Service;
/**
* 密码登录
*/
@Service("password")
public class PasswordLoginServiceImpl implements LoginService {
@Override
public String login(LoginUser loginUser) {
LoginUserToPassword loginUserToPassword = (LoginUserToPassword) loginUser;
// 校验图形验证码
if (!"123456".equals(loginUserToPassword.getImageCode())) {
return "login fail. imageCode error";
}
// 校验用户名密码
if (!"admin".equals(loginUserToPassword.getUsername()) || !"123456".equals(loginUserToPassword.getPassword())) {
return "login fail. username or password error";
}
return "success";
}
}
(手机短信登录)
import com.hezy.pojo.LoginUser;
import com.hezy.pojo.LoginUserToPhone;
import org.springframework.stereotype.Service;
/**
* 手机短信登录
*/
@Service("phone")
public class PhoneLoginServiceImpl implements LoginService {
@Override
public String login(LoginUser loginUser) {
LoginUserToPhone loginUserToPhone = (LoginUserToPhone) loginUser;
// 校验图形验证码
if (!"123456".equals(loginUserToPhone.getImageCode())) {
return "login fail. imageCode error";
}
// 校验用户名密码
if (!"181XXXX1234".equals(loginUserToPhone.getPhone()) || !"phone_code".equals(loginUserToPhone.getCode())) {
return "login fail. username or password error";
}
return "success";
}
}
(微信扫码登录)
import com.hezy.pojo.LoginUser;
import com.hezy.pojo.LoginUserToWechat;
import org.springframework.stereotype.Service;
/**
* 微信扫码登录
*/
@Service("wechat")
public class WechatLoginServiceImpl implements LoginService {
@Override
public String login(LoginUser loginUser) {
LoginUserToWechat loginUserToWechat = (LoginUserToWechat) loginUser;
// 校验图形验证码
if (!"123456".equals(loginUserToWechat.getImageCode())) {
return "login fail. imageCode error";
}
// 校验用户名密码
if (!"xiaohe".equals(loginUserToWechat.getNickname()) || !"wx_code".equals(loginUserToWechat.getCode())) {
return "login fail. username or password error";
}
return "success";
}
}
这里给每个实现类都设置了一个 Bean 名称,在 controller 中,可以使用自动装配,根据前端传入的 type 字段调用具体的实现类登录方法执行。当然,前端传入的 type 字段值要和具体实现类的 Service 名称对应。
(自动装配)
@Autowired
private Map<String, LoginService> loginServiceMap;
(调用执行)
return loginServiceMap.get(loginUserDTO.getType()).login(loginUser);
场景一
基于上述背景,可以发现具体实现类中都有对父类图形验证码字段值的校验,有冗余代码。
// 校验图形验证码
if (!"123456".equals(loginUserToPhone.getImageCode())) {
return "login fail. imageCode error";
}
为了减少冗余代码,可以有两个解决思路,1)将校验代码放入到父类对象里,如下:
import lombok.Data;
import java.io.Serializable;
/**
* 登录用户父类
*/
@Data
public class LoginUser implements Serializable {
/**
* 图形验证码
*/
private String imageCode;
/**
* 校验图形验证码
*/
public Boolean checkImageCode() {
if (!"123456".equals(imageCode)) {
return false;
}
return true;
}
}
具体实现类中直接调用对象方法校验,根据返回值作判断
// 校验图形验证码
if (loginUser.checkImageCode()) {
return "login fail. imageCode error";
}
2)使用抽象类,将具体实现类中的重复代码移到抽象类中,如下:
(在接口与具体实现类中间加一层抽象类,放入所有具体实现类的重复代码)
/**
* 登录接口抽象类
*/
public abstract class AbstractLoginService implements LoginService {
/**
* 校验图形验证码
*/
public Boolean checkImageCode(String imageCode) {
if (!"123456".equals(imageCode)) {
return false;
}
return true;
}
}
具体实现类不直接实现接口,而是继承抽象类,使用抽象类中的校验方法
场景二
现在假设需要新开发一个打印用户信息的功能,需要根据不同登录类型的用户分别生成对应的文件,不同登录类型有不同的文件模板。
import com.hezy.service.PrintService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 打印接口
*/
@RestController
@RequestMapping("/print")
public class PrintController {
@Autowired
private Map<String, PrintService> printServiceMap;
@GetMapping
public String print(String type) {
return printServiceMap.get(type).print();
}
}
这个实现很简单,直接在具体实现类中实现该方法即可
(打印接口)
/**
* 打印接口
*/
public interface PrintService {
/**
* 打印方法
*/
String print();
}
(账号密码登录)
import com.hezy.pojo.LoginUserToPassword;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 账号密码用户打印
*/
@Service("password")
public class PasswordPrintServiceImpl implements PrintService {
@Override
public String print() {
// 1.获取模板路径
String path = "【账号密码登录】用户模板路径";
// 2.获取模板数据
Map<String, LoginUserToPassword> data = new HashMap<>();
LoginUserToPassword loginUserToPassword = new LoginUserToPassword();
loginUserToPassword.setUsername("admin");
loginUserToPassword.setPassword("123456");
data.put("loginUserToPassword", loginUserToPassword);
// 3.调用打印服务,返回
return path + data;
}
}
(手机短信登录)
import com.hezy.pojo.LoginUserToPhone;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 手机短信用户打印
*/
@Service("phone")
public class PhonePrintServiceImpl implements PrintService {
@Override
public String print() {
// 1.获取模板路径
String path = "【手机短信登录】用户模板路径";
// 2.获取模板数据
Map<String, LoginUserToPhone> data = new HashMap<>();
LoginUserToPhone loginUserToPhone = new LoginUserToPhone();
loginUserToPhone.setPhone("181XXXX1234");
loginUserToPhone.setCode("phone_code");
data.put("loginUserToPhone", loginUserToPhone);
// 3.调用打印服务,返回
return path + data.toString();
}
}
(微信扫码登录)
import com.hezy.pojo.LoginUserToWechat;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 微信扫码用户打印
*/
@Service("wechat")
public class WechatPrintServiceImpl implements PrintService {
@Override
public String print() {
// 1.获取模板路径
String path = "【微信扫码登录】用户模板路径";
// 2.获取模板数据
Map<String, LoginUserToWechat> data = new HashMap<>();
LoginUserToWechat loginUserToWechat = new LoginUserToWechat();
loginUserToWechat.setNickname("xiaohe");
loginUserToWechat.setCode("wx_code");
data.put("loginUserToWechat", loginUserToWechat);
// 3.调用打印服务,返回
return path + data;
}
}
但是又有问题,在具体实现打印方法中,所有实现类的第三步是相同的,区别只有第一步、第二步,第三步是重复代码,上述 Demo 虽然只有一行代码,但在实际开发中可能有较多的冗余代码。
对此,我们可以将该方法放入到抽象类中,对特异的第一步,第二步创建两个抽象方法,然具体实现类实现,如下:
(打印服务抽象类)
import java.util.Map;
/**
* 打印服务抽象类
*/
public abstract class AbstractPrintService implements PrintService {
/**
* 获取模板路径
*
* @return 具体实现类的模板路径
*/
public abstract String getPath();
/**
* 获取模板数据
*
* @return 具体实现类的模板数据
*/
public abstract Map<String, Object> getData();
@Override
public String print() {
// 1.获取模板路径
String path = getPath();
// 2.获取模板数据
Map<String, Object> data = getData();
// 3.调用打印服务,返回
return path + data;
}
}
具体实现类就省事了,只需实现这两个抽象方法即可
(账号密码用户打印)
import com.hezy.pojo.LoginUserToPassword;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 账号密码用户打印
*/
@Service("password")
public class PasswordPrintServiceImpl extends AbstractPrintService {
@Override
public String getPath() {
return "【账号密码登录】用户模板路径";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
LoginUserToPassword loginUserToPassword = new LoginUserToPassword();
loginUserToPassword.setUsername("admin");
loginUserToPassword.setPassword("123456");
data.put("loginUserToPassword", loginUserToPassword);
return data;
}
}
(手机短信用户打印)
import com.hezy.pojo.LoginUserToPhone;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 手机短信用户打印
*/
@Service("phone")
public class PhonePrintServiceImpl extends AbstractPrintService {
@Override
public String getPath() {
return "【手机短信登录】用户模板路径";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
LoginUserToPhone loginUserToPhone = new LoginUserToPhone();
loginUserToPhone.setPhone("181XXXX1234");
loginUserToPhone.setCode("phone_code");
data.put("loginUserToPhone", loginUserToPhone);
return data;
}
}
(微信扫码用户打印)
import com.hezy.pojo.LoginUserToWechat;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 微信扫码用户打印
*/
@Service("wechat")
public class WechatPrintServiceImpl extends AbstractPrintService {
@Override
public String getPath() {
return "【微信扫码登录】用户模板路径";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
LoginUserToWechat loginUserToWechat = new LoginUserToWechat();
loginUserToWechat.setNickname("xiaohe");
loginUserToWechat.setCode("wx_code");
data.put("loginUserToWechat", loginUserToWechat);
return data;
}
}
启动项目,验证。可见接口返回是具体实现类的打印内容
(账号密码登录)
(手机短信登录)
总结
本文介绍了在 Java 开发中,抽象类的两个作用,分别是:
-
抽取具体实现类中的重复代码,减少冗余代码;
-
定义具体实现类行为,抽取公共部分为抽象方法,保证方法实现逻辑不被改变;