Java中抽象类的作用

8 阅读7分钟

说明:本文基于场景介绍抽象类在实际 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 开发中,抽象类的两个作用,分别是:

  • 抽取具体实现类中的重复代码,减少冗余代码;

  • 定义具体实现类行为,抽取公共部分为抽象方法,保证方法实现逻辑不被改变;

首次发布

hezhongying.blog.csdn.net/article/det…