美文网首页java
抽象类使用场景

抽象类使用场景

作者: 修行者12138 | 来源:发表于2023-02-18 18:14 被阅读0次

    抽象类和接口的区别

    定位

    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() {}
    }
    
    

    相关文章

      网友评论

        本文标题:抽象类使用场景

        本文链接:https://www.haomeiwen.com/subject/nsjbkdtx.html