抽象类和接口的区别
定位
- 抽象类是特殊的类,不能被实例化,只能被子类继承。继承体现的是is-a关系,所以抽象类体现的也是is-a关系,即“是什么”,比如鸟是一种动物。
- 接口体现的是has-a关系,即“有什么”,比如动物拥有“叫”的行为。接口也经常被称为协议,表示具有哪些功能,调用方只关心接口定义,不关心具体实现。
解决的问题
- 抽象类解决的是代码复用的问题,不同子类的公共代码可以放到父类,非公共代码由子类自行实现。
- 接口解决的是约定协议和解耦的问题,上下游约定好协议,不关心具体内容,做到了协议和实现的解耦。
代码示例
日志收集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() {}
}
网友评论