抽象类使用场景

抽象类和接口的区别

定位

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容