java

抽象类使用场景

2023-02-18  本文已影响0人  修行者12138

抽象类和接口的区别

定位

  1. 抽象类是特殊的类,不能被实例化,只能被子类继承。继承体现的是is-a关系,所以抽象类体现的也是is-a关系,即“是什么”,比如鸟是一种动物。
  2. 接口体现的是has-a关系,即“有什么”,比如动物拥有“叫”的行为。接口也经常被称为协议,表示具有哪些功能,调用方只关心接口定义,不关心具体实现。

解决的问题

  1. 抽象类解决的是代码复用的问题,不同子类的公共代码可以放到父类,非公共代码由子类自行实现。
  2. 接口解决的是约定协议和解耦的问题,上下游约定好协议,不关心具体内容,做到了协议和实现的解耦。

代码示例

日志收集SDK

背景

假设现在要给业务团队提供一个请求日志收集SDK,把业务团队的请求日志收集并发送到日志平台。
请求日志包含了请求入参(包含header和具体内容)、用户信息、后端应用信息(appId,应用的唯一标识),获取请求入参可以由SDK自行完成,但是用户信息和后端应用信息,需要业务团队自行提供。

代码示例

下列代码中,为了获取用户信息和后端应用信息,提供了抽象的getAppInfo和getUserInfo方法,强制要求业务方自行实现。

import io.swagger.annotations.ApiOperation;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import javax.servlet.http.HttpServletRequest;

@Slf4j
public abstract class LogAspect {

    @Pointcut("within(@org.springframework.web.bind.annotation *) || within(@org.springframework.stereotype.Controller *)")
    public void controllerPointcut(){}

    @Around("controllerPointcut")
    public Object collectLog(ProceedingJoinPoint joinPoint) throws Throwable{
        // 获取当前HttpServletRequest
        HttpServletRequest httpServletRequest = null;

        // 收集信息
        LogRecord logRecord = LogRecord.builder()
                .sourceAppId(getAppInfo().getAppId())
                .userId(getUserInfo().getUserId())
                .remoteAddr(httpServletRequest.getRemoteAddr())
                .xff(getFirstNotBlankHeader(httpServletRequest, "x-forwarded-for", "X-Forwarded-For"))
                .ua(getFirstNotBlankHeader(httpServletRequest, "user-agent", "User-Agent"))
                .referer(getFirstNotBlankHeader(httpServletRequest, "referer", "Referer"))
                .requestUrl(httpServletRequest.getRequestURL().toString())
                .requestDesc(getRequestDesc(joinPoint))
                .build();

        // 上传到日志平台
        // upload(logRecord);

        return joinPoint.proceed();
    }

    /**
     * 获取应用信息
     */
    abstract AppInfo getAppInfo();

    /**
     * 获取用户信息
     */
    abstract UserInfo getUserInfo();
    
    /**
     * 兼容headerName大小写不一致的问题。比如ua的headerName,可能是user-agent,或者User-Agent
     * @return 第一个不为空的headerValue,不存在则返回null
     */
    private String getFirstNotBlankHeader(HttpServletRequest httpServletRequest, String... headerNames) {
        if (headerNames == null) {
            return null;
        }
        for (String headerName: headerNames) {
            String headerValue = httpServletRequest.getHeader(headerName);
            if (StringUtils.isNoneBlank(headerValue)) {
                return headerValue;
            }
        }
        return null;
    }

    /**
     * 如果使用swagger标注方法用途,返回ApiOperation注解标注的方法用途
     */
    private String getRequestDesc(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        ApiOperation annotation = signature.getMethod().getAnnotation(ApiOperation.class);
        return annotation != null ? annotation.value() : null;
    }

    @Data
    @Builder
    class LogRecord {
        private String sourceAppId;
        private String userId;
        private String remoteAddr;
        private String xff;
        private String ua;
        private String referer;
        private String requestUrl;
        private String requestDesc;
    }

    @Data
    public class AppInfo {
        private String appId;

        /**
         * 手动实现builder,避免SDK使用时还需要依赖lombok
         */
        public AppInfo setAppId(final String appId) {
            this.appId = appId;
            return this;
        }
    }

    @Data
    public class UserInfo {
        private String userId;

        public UserInfo setUserId(final String userId) {
            this.userId = userId;
            return this;
        }
    }
}

业务方使用示例

@Configuration
public class LogConfig {

    @Bean
    public LogAspect generateLogAspect() {
        return new LogAspect() {
            @Override
            LogAspect.AppInfo getAppInfo() {
                return new AppInfo().setAppId("appId");
            }

            @Override
            LogAspect.UserInfo getUserInfo() {
                return new UserInfo().setUserId("userId");
            }
        };
    }
}

模板方法模式

抽象类常用于模板方法模式,假设有a、b、c 3个方法,b方法需要由子类自行实现,代码示例如下

public abstract class TemplatePatternDemo {
    public void execute() {
        a();
        b();
        c();
    }
    
    private void a() {}
    
    abstract void b();
    
    private void c() {}
}

上一篇下一篇

猜你喜欢

热点阅读