乐趣区

设计模式之美二-面向对象3

7. 接口 vs 抽象类的区别,使用普通类模拟抽象类和接口

7.1 抽象类
  • 定义: abstract 关键词修饰
  • 抽象类特征:
    1. 抽象类不能被实例化,只能被继承(extends)
    1. 抽象类可以包含属性和方法,方法既可以是包含代码实现的方法,也可以是不含代码实现的方法。不包含代码实现的方法叫做抽象方法
    1. 子类继承抽象类,必须实现抽象类中的所有抽象方法
  • Demo 记录日志抽象类

// 抽象类
public abstract class Logger {
  private String name;
  private boolean enabled;
  private Level minPermittedLevel;

  // 构造函数 1- 函数名与类名相同 2- 不用定义返回值(不用加 void)3- 不用写 return
  public Logger(String name, boolean enabled, Level minPermittedLevel) {
    this.name = name;
    this.enabled = enabled;
    this.minPermittedLevel = minPermittedLevel;
  }
  
  public void log(Level level, String message) {boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
    if (!loggable) return;
    doLog(level, message);
  }
  // 抽象方法
  protected abstract void doLog(Level level, String message);
}
// 抽象类的子类:输出日志到文件
public class FileLogger extends Logger {
  private Writer fileWriter;

  public FileLogger(String name, boolean enabled,
    Level minPermittedLevel, String filepath) {super(name, enabled, minPermittedLevel);
    this.fileWriter = new FileWriter(filepath); 
  }
  
  @Override
  public void doLog(Level level, String mesage) {
    // 格式化 level 和 message, 输出到日志文件
    fileWriter.write(...);
  }
}
// 抽象类的子类: 输出日志到消息中间件(比如 kafka)
public class MessageQueueLogger extends Logger {
  private MessageQueueClient msgQueueClient;
  
  public MessageQueueLogger(String name, boolean enabled,
    Level minPermittedLevel, MessageQueueClient msgQueueClient) {super(name, enabled, minPermittedLevel);
    this.msgQueueClient = msgQueueClient;
  }
  
  @Override
  protected void doLog(Level level, String mesage) {
    // 格式化 level 和 message, 输出到消息中间件
    msgQueueClient.send(...);
  }
}
7.2 接口
  • 定义:interface 关键字
  • 特征:
    1. 接口不能被实例化,只能被实现(implements)
    1. 接口不能包含属性(也就是成员变量)和普通方法
    1. 接口只能声明抽象方法,即方法不能包含代码实现
    1. 类实现接口的时候,必须实现接口声明的所有方法
  • Demo

// 接口
public interface Filter {void doFilter(RpcRequest req) throws RpcException;
}
// 接口实现类:鉴权过滤器
public class AuthencationFilter implements Filter {
  @Override
  public void doFilter(RpcRequest req) throws RpcException {//... 鉴权逻辑..}
}
// 接口实现类:限流过滤器
public class RateLimitFilter implements Filter {
  @Override
  public void doFilter(RpcRequest req) throws RpcException {//... 限流逻辑...}
}
// 过滤器使用 demo
public class Application {// filters.add(new AuthencationFilter());
  // filters.add(new RateLimitFilter());
  private List<Filter> filters = new ArrayList<>();
  
  public void handleRpcRequest(RpcRequest req) {
    try {for (Filter filter : fitlers) {filter.doFilter(req);
      }
    } catch(RpcException e) {// ... 处理过滤结果...}
    // ... 省略其他处理逻辑...
  }
}
7.3 基于接口而非实现编程,基于抽象而非实现编程(两句话一个意思)
  • 接口:本质上接口是一组“协议”或“约定”,是功能提供者 提供给 使用者 的一个“功能列表”
  • 在具体的代码层面理解这句话,接口可以理解为编程语言中的接口或者抽象类
  • 注意问题:
    1. 函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()
    1. 封装具体的实现细节。比如,跟阿里云相关的特殊上传(或下载)流程不应该暴露给调用者。我们对上传(或下载)流程进行封装,对外提供一个包裹所有上传(或下载)细节的方法,给调用者使用。
7.4 多用组合少用继承
    1. 为什么不推荐使用继承?继承是面向对象的四大特性之一,用来表示类之间的 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但 继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少用,甚至不用继承。
    1. 组合相比继承有哪些优势?继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。
    1. 如何判断该用组合还是继承?尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,我们还是要根据具体的情况,来选择该用继承还是组合。如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。反之,我们就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。
退出移动版