03.接口vs抽象类比较

03.接口vs抽象类比较

目录介绍
  • 01.面向对象设计特性
    • 1.1 抽象和接口特性
    • 1.2 一些问题思考
    • 1.3 抽象的设计思想
    • 1.4 抽象思想案例
  • 02.抽象类介绍
    • 2.1 看抽象案例
    • 2.2 抽象类特点
    • 2.3 抽象类设计注意要点
    • 2.4 抽象的思想
    • 2.5 模拟抽象类
  • 03.接口设计介绍
    • 3.1 看接口案例
    • 3.2 接口的特点
    • 3.3 接口设计注意点
    • 3.4 接口的思想
    • 3.5 Marker Interface
    • 3.6 模拟接口设计
  • 04.解决什么编程问题
    • 4.1 抽象类的由来
    • 4.2 接口的由来
  • 05.抽象类VS接口
    • 5.1 理解抽象和接口
    • 5.2 语法上不同
    • 5.3 编程角度不同
    • 5.4 通俗理解两者区别
    • 5.5 设计层次上区别
  • 06.如何选择场景
    • 6.1 判断标准很简单
    • 6.2 场景上的区别
    • 6.3 一些具体的例子
    • 6.4 开发总结一下

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

yccoding.com/

01.面向对象设计特性

1.1 抽象和接口特性

在面向对象编程中,抽象类和接口是两个经常被用到的语法概念,是面向对象四大特性,以及很多设计模式、设计思想、设计原则编程实现的基础。

比如,我们可以使用接口来实现面向对象的抽象特性、多态特性和基于接口而非实现的设计原则,使用抽象类来实现面向对象的继承特性和模板设计模式等等。

并不是所有的面向对象编程语言都支持这两个语法概念,比如,C++ 这种编程语言只支持抽象类,不支持接口;而像 Python 这样的动态编程语言,既不支持抽象类,也不支持接口。

尽管有些编程语言没有提供现成的语法来支持接口和抽象类,我们仍然可以通过一些手段来模拟实现这两个语法概念。

1.2 一些问题思考

这两个语法概念不仅在工作中经常会被用到,在面试中也经常被提及。比如,"接口和抽象类的区别是什么?什么时候用接口?什么时候用抽象类?抽象类和接口存在的意义是什么?能解决哪些编程问题?"等等。

1.3 抽象的设计思想

抽象思想是指将事物或概念从具体的、特定的细节中抽离出来,关注其普遍性、共性和本质特征的思维方式。它是一种对事物进行概括、归纳和提炼的思考方式,通过忽略细节和个别差异,抓住事物的本质和共同点,以更高层次的概念和模型来理解和描述事物。

1.4 抽象思想案例

代码如下所示。抽象类除了有抽象类特性之外,还可以解决代码复用问题。

java 复制代码
/*抽象类作为参数的时候如何进行调用*/
abstract class Animal {

    protected int x;
    private int y;

    // 定义一个抽象方法
    public abstract void eat() ;

    public void func2() {
        System.out.println("func2");
    }
}

// 定义一个类,猫
class Cat extends Animal {
    public void eat(){
        System.out.println("吃鱼.................") ;
    }
}

// 定义一个类,狗
class Dog extends Animal {
    public void eat(){
        System.out.println("吃骨头.................") ;
    }
}

// 定义一个类,动物类
class AnimalDemo {
    public void method(Animal a) {
        a.eat() ;
    }
}

// 测试类
class ArgsDemo2  {
    public static void main(String[] args) {
        // 创建AnimalDemo的对象
        AnimalDemo ad = new AnimalDemo() ;
        // 对Animal进行间接实例化
        // Animal a = new Cat() ;
        // Animal a = new Dog() ;
        Cat a = new Cat() ;
        // 调用method方法
        ad.method(a) ;
    }
}

02.抽象类介绍

2.1 看抽象案例

不同的编程语言对接口和抽象类的定义方式可能有些差别,但差别并不会很大。首先来看一下,在 Java 这种编程语言中,我们是如何定义抽象类的。

下面这段代码是一个比较典型的抽象类的使用场景(模板设计模式)。

  • Logger 是一个记录日志的抽象类,FileLogger 和 MessageQueueLogger 继承 Logger,分别实现两种不同的日志记录方式:记录日志到文件中和记录日志到消息队列中。
  • FileLogger 和 MessageQueueLogger 两个子类复用了父类方法,但因为这两个子类写日志的方式不同,它们又各自重写了父类中的 doLog() 方法。
java 复制代码
// 抽象类
public abstract class Logger {
  private String name;
  private boolean enabled;
  private Level minPermittedLevel;

  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.writer = 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(...);
  }
}

2.2 抽象类特点

抽象类是一种在面向对象编程中的概念,它是不能被实例化的类,只能被继承。抽象类用于定义一组抽象方法和可能的具体方法,以提供一种通用的接口和行为,供其子类实现和继承。

通过上面的这个例子来看一下,抽象类具有哪些特性。

  1. 抽象类不允许被实例化,只能被继承。也就是说,你不能 new 一个抽象类的对象出来(Logger logger = new Logger(...); 会报编译错误)。
  2. 抽象类可以包含属性和方法。方法既可以包含代码实现(比如 Logger 中的 log() 方法),也可以不包含代码实现(比如 Logger 中的 doLog() 方法)。不包含代码实现的方法叫作抽象方法。
  3. 子类继承抽象类,必须实现抽象类中的所有抽象方法。对应到例子代码中就是,所有继承 Logger 抽象类的子类,都必须重写 doLog() 方法。

抽象类提供了一种抽象的概念和接口,用于定义一组相关的类的共同行为和属性。它可以作为一种模板或基类,为子类提供一致的接口和行为,实现代码的复用和多态性

2.3 抽象类设计注意要点

如果想要设计这样一个类,该类包含一个特别的成员方法,方法的具体实现由它的子类确定,那么可以在父类中声明该方法为抽象方法

Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,则该类必须声明为抽象类
  • 子类必须重写父类的抽象方法,否则自身也必须声明为抽象类

2.4 抽象的思想

抽象特性的定义讲完了,我们再来看一下,抽象的意义是什么?它能解决什么编程问题?

实际上,如果上升一个思考层面的话,抽象及其前面讲到的封装都是人类处理复杂性的有效手段。

在面对复杂系统的时候,人脑能承受的信息复杂程度是有限的,所以我们必须忽略掉一些非关键性的实现细节。抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。

抽象作为一个非常宽泛的设计思想,很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。

举个简单例子,比如 getAliPictureUrl() 就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要随之被修改。相反,如果我们定义一个比较抽象的函数,比如叫作 getPictureUrl(),那即便内部存储方式修改了,我们也不需要修改命名。

2.5 模拟抽象类

在 Python、Ruby 这些动态语言中,不仅没有接口的概念,也没有类似 abstract、virtual 这样的关键字来定义抽象类,那该如何实现上面的讲到的 抽象类 的设计思路呢?

实际上,除了用抽象类来模拟接口之外,还可以用普通类来模拟接口。具体的 Java 代码实现如下所示。

java 复制代码
public class MockInteface {
  protected MockInteface() {}
  public void funcA() {
    throw new MethodUnSupportedException();
  }
}

类中的方法必须包含实现,这个不符合接口的定义。但是,我们可以让类中的方法抛出 MethodUnSupportedException 异常,来模拟不包含实现的接口,并且能强迫子类在继承这个父类的时候,都去主动实现父类的方法,否则就会在运行时抛出异常。

那又如何避免这个类被实例化呢?实际上很简单,我们只需要将这个类的构造函数声明为 protected 访问权限就可以了。

03.接口设计介绍

3.1 看接口案例

再来看一下,在 Java 这种编程语言中,我们如何定义接口。

java 复制代码
// 接口
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) {
      // ...处理过滤结果...
    }
    // ...省略其他处理逻辑...
  }
}

上面这段代码是一个比较典型的接口的使用场景。通过 Java 中的 interface 关键字定义了一个 Filter 接口。AuthenticationFilter 和 RateLimitFilter 是接口的两个实现类,分别实现了对 RPC 请求鉴权和限流的过滤功能。

3.2 接口的特点

代码非常简洁。结合代码再来看一下,接口都有哪些特性。

  • 接口不能包含属性(也就是成员变量)。
  • 接口只能声明方法,方法不能包含代码实现。
  • 类实现接口的时候,必须实现接口中声明的所有方法。

接口是一种抽象的概念,用于定义一组方法的契约,而不涉及具体的实现。接口定义了类应该具有的方法和行为,以提供一种通用的接口,供类来实现。

3.3 接口设计注意点

3.4 接口的思想

3.5 Marker Interface

接口的职责也不仅仅限于抽象方法的集合,其实有各种不同的实践。

有一类没有任何方法的接口,通常叫作 Marker Interface,顾名思义,它的目的就是为了声明某些东西,比如我们熟知的 Cloneable、Serializable 等。这种用法,也存在于业界其他的 Java 产品代码中。

3.6 模拟接口设计

如果你熟悉的是 C++ 这种编程语言,你可能会说,C++ 只有抽象类,并没有接口,那从代码实现的角度上来说,是不是就无法实现 接口 的设计思路了呢?

先来回忆一下接口的定义:接口中没有成员变量,只有方法声明,没有方法实现,实现接口的类必须实现接口中的所有方法。

只要满足这样几点,从设计的角度上来说,我们就可以把它叫作接口。实际上,要满足接口的这些语法特性并不难。

在下面这段 C++ 代码中,就用抽象类模拟了一个接口(下面这段代码实际上是策略模式中的一段代码)。

cpp 复制代码
class Strategy { // 用抽象类模拟接口
  public:
    ~Strategy();
    virtual void algorithm()=0;
  protected:
    Strategy();
};

抽象类 Strategy 没有定义任何属性,并且所有的方法都声明为 virtual 类型(等同于 Java 中的 abstract 关键字)。

这样,所有的方法都不能有代码实现,并且所有继承这个抽象类的子类,都要实现这些方法。从语法特性上来看,这个抽象类就相当于一个接口。

04.解决什么编程问题

4.1 抽象类的由来

抽象类也是为代码复用而生的。多个子类可以继承抽象类中定义的属性和方法,避免在子类中,重复编写相同的代码。

不过,既然继承本身就能达到代码复用的目的,而继承也并不要求父类一定是抽象类,那我们不使用抽象类,照样也可以实现继承和复用。从这个角度上来讲,我们貌似并不需要抽象类这种语法呀。

那抽象类除了解决代码复用的问题,还有什么其他存在的意义吗?

还是拿之前那个打印日志的例子。我们先对上面的代码做下改造。在改造之后的代码中,Logger 不再是抽象类,只是一个普通的父类,删除了 Logger 中 log()、doLog() 方法,新增了 isLoggable() 方法。FileLogger 和 MessageQueueLogger 还是继承 Logger 父类,以达到代码复用的目的。具体的代码如下:

java 复制代码
// 父类:非抽象类,就是普通的类. 删除了log(),doLog(),新增了isLoggable().
public class Logger {
  private String name;
  private boolean enabled;
  private Level minPermittedLevel;

  public Logger(String name, boolean enabled, Level minPermittedLevel) {
    //...构造函数不变,代码省略...
  }

  protected boolean isLoggable() {
    boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
    return loggable;
  }
}
// 子类:输出日志到文件
public class FileLogger extends Logger {
  private Writer fileWriter;

  public FileLogger(String name, boolean enabled,
    Level minPermittedLevel, String filepath) {
    //...构造函数不变,代码省略...
  }
  
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到日志文件
    fileWriter.write(...);
  }
}
// 子类: 输出日志到消息中间件(比如kafka)
public class MessageQueueLogger extends Logger {
  private MessageQueueClient msgQueueClient;
  
  public MessageQueueLogger(String name, boolean enabled,
    Level minPermittedLevel, MessageQueueClient msgQueueClient) {
    //...构造函数不变,代码省略...
  }
  
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到消息中间件
    msgQueueClient.send();
  }
}

这个设计思路虽然达到了代码复用的目的,但是无法使用多态特性了。像下面这样编写代码,就会出现编译错误,因为 Logger 中并没有定义 log() 方法。

java 复制代码
Logger logger = new FileLogger("access-log", true, Level.WARN, "/users/yc/access.log");
logger.log(Level.ERROR, "This is a test log message.");

你可能会说,这个问题解决起来很简单啊。在 Logger 父类中,定义一个空的 log() 方法,让子类重写父类的 log() 方法,实现自己的记录日志的逻辑,不就可以了吗?

java 复制代码
public class Logger {
  // ...省略部分代码...
  public void log(Level level, String mesage) { // do nothing... }
}
public class FileLogger extends Logger {
  // ...省略部分代码...
  @Override
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到日志文件
    fileWriter.write(...);
  }
}
public class MessageQueueLogger extends Logger {
  // ...省略部分代码...
  @Override
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到消息中间件
    msgQueueClient.send(...);
  }
}

这个设计思路能用,但是,它显然没有之前通过抽象类的实现思路优雅。为什么这么说呢?主要有以下几点原因。

  1. 在 Logger 中定义一个空的方法,会影响代码的可读性。如果我们不熟悉 Logger 背后的设计思想,代码注释又不怎么给力,我们在阅读 Logger 代码的时候,就可能对为什么定义一个空的 log() 方法而感到疑惑,需要查看 Logger、FileLogger、MessageQueueLogger 之间的继承关系,才能弄明白其设计意图。
  2. 当创建一个新的子类继承 Logger 父类的时候,我们有可能会忘记重新实现 log() 方法。之前基于抽象类的设计思路,编译器会强制要求子类重写 log() 方法,否则会报编译错误。我们举的例子比较简单,Logger 中的方法不多,代码行数也很少。但是,如果 Logger 有几百行,有 n 多方法,除非你对 Logger 的设计非常熟悉,否则忘记重新实现 log() 方法,也不是不可能的。
  3. Logger 可以被实例化,换句话说,我们可以 new 一个 Logger 出来,并且调用空的 log() 方法。这也增加了类被误用的风险。当然,这个问题可以通过设置私有的构造函数的方式来解决。不过,显然没有通过抽象类来的优雅。

4.2 接口的由来

为什么需要接口?它能够解决什么编程问题?

抽象类更多的是为了代码复用,而接口就更侧重于解耦。接口是对行为的一种抽象,相当于一组协议或者契约,调用者只需要关注抽象的接口,不需要了解具体的实现,具体的实现代码对调用者透明。接口实现了约定和实现相分离,可以降低代码间的耦合性,提高代码的可扩展性。

实际上,接口是一个比抽象类应用更加广泛、更加重要的知识点。比如,经常提到的"基于接口而非实现编程",就是一条几乎天天会用到,并且能极大地提高代码的灵活性、扩展性的设计思想。

05.抽象类VS接口

5.1 理解抽象和接口

这两个语法概念不仅在工作中经常会被用到,在面试中也经常被提及。比如,"接口和抽象类的区别是什么?什么时候用接口?什么时候用抽象类?抽象类和接口存在的意义是什么?能解决哪些编程问题?"等等。

abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,避免使用时在进行抽象类定义时对于 abstract class和interface的选择随意。

其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。

本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

5.2 语法上不同

抽象类

java 复制代码
abstract class Student {    
   abstract void method1();    
   abstract void method2();    
   public void method3() {
       System.out.println("func2");
   }
}    

接口

java 复制代码
interface Student {   
   //接口中的变量其实就是常量,默认被final修饰
   int age = 10; 
   void method1();    
   void method2();    
}    

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstract的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

抽象类实际上就是类,只不过是一种特殊的类,这种类不能被实例化为对象,只能被子类继承。我们知道,继承关系是一种 is-a 的关系,那抽象类既然属于类,也表示一种 is-a 的关系。相对于抽象类的 is-a 关系来说,接口表示一种 has-a 关系,表示具有某些功能。对于接口,有一个更加形象的叫法,那就是协议(contract)。

两者语法上的区别

  • 抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,
  • 但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。
  • 在某种程度上来说,接口是抽象类的特殊化。
  • 对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。

5.3 编程角度不同

abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,不过在JDK1.8中可以使用default关键字实现默认方法。

java 复制代码
interface InterfaceA {
    default void foo() {
        System.out.println("InterfaceA foo");
    }
}

在 Java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为Java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

5.4 通俗理解两者区别

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。从设计理念上,接口反映的是 "like-a" 关系,抽象类反映的是 "is-a" 关系。

抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话......),他们的抽象类是人。说明,他们都是人。

人可以吃东西,狗也可以吃东西,你可以把"吃东西"定义成一个接口,然后让这些类去实现它.

所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。

5.5 设计层次上区别

抽象层次不同:抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

跨域不同:抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。

设计层次不同

  • 对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!
  • 但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

06.如何选择场景

6.1 判断标准很简单

实际上,判断的标准很简单。

  1. 如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题,就用抽象类;
  2. 如果我们要表示一种 has-a 关系,并且是为了解决抽象而非代码复用的问题,那就可以使用接口。

从类的继承层次上来看,抽象类是一种自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(也就是抽象类)。而接口正好相反,它是一种自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再去考虑具体的实现。

6.2 场景上的区别

抽象类和接口在设计上有一些区别,尽管它们都是面向对象编程中的抽象概念。以下是它们之间的一些主要区别:

  1. 实现方式:抽象类通过继承的方式被子类实现,而接口通过实现的方式被类实现。一个类只能继承一个抽象类,但可以实现多个接口。
  2. 方法实现:抽象类可以包含具体方法的实现,而接口只能包含方法的声明,没有具体的实现。类继承抽象类时,可以直接继承具体方法的实现,而实现接口时,必须提供方法的具体实现。
  3. 关注点:抽象类更适合用于描述一种 "是什么" 的关系,即类与类之间的继承关系。接口更适合用于描述一种 "能做什么" 的关系,即类具有哪些方法和行为。
  4. 灵活性:抽象类可以包含实例变量,而接口只能包含常量。抽象类可以提供一些默认的实现,而接口只能定义方法的契约,没有默认实现。
  5. 使用场景:抽象类通常用于描述一组相关的类,提供一种通用的基类,而接口通常用于定义一组方法的契约,用于实现多态性和解耦合。

6.3 一些具体的例子

那么在实际开发中应该如何选择抽象类和接口的案例:

  1. 如果需要提供一组相关类的通用行为和属性,可以使用抽象类。比如,Android中要抽象出公共的BaseActivity,可以做到子类复用!
  2. 如果需要定义一组方法的契约,以实现多态性和解耦合,可以使用接口。比如,Android中要定义MVP的View和Presenter的交互接口,则要用接口!
  3. 在某些情况下,抽象类和接口可以结合使用,以满足更复杂的设计需求。

6.4 开发总结一下

  1. 抽象类和接口的语法特性

抽象类不允许被实例化,只能被继承。它可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫作抽象方法。子类继承抽象类,必须实现抽象类中的所有抽象方法。接口不能包含属性,只能声明方法,方法不能包含代码实现。类实现接口的时候,必须实现接口中声明的所有方法。

  1. 抽象类和接口存在的意义

抽象类是对成员变量和方法的抽象,是一种 is-a 关系,是为了解决代码复用问题。接口仅仅是对方法的抽象,是一种 has-a 关系,表示具有某一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。

  1. 抽象类和接口的应用场景区别

什么时候该用抽象类?什么时候该用接口?实际上,判断的标准很简单。如果要表示一种 is-a 的关系,并且是为了解决代码复用问题,我们就用抽象类;如果要表示一种 has-a 关系,并且是为了解决抽象而非代码复用问题,那我们就用接口。

  1. 从语法特性上对比,这两者有比较大的区别:

语法特性的区别:比如抽象类中可以定义属性、方法的实现,而接口中不能定义属性,方法也不能包含代码实现等等。

设计角度的区别:抽象类实际上就是类,只不过是一种特殊的类,这种类不能被实例化为对象,只能被子类继承。我们知道,继承关系是一种 is-a 的关系,那抽象类既然属于类,也表示一种 is-a 的关系。相对于抽象类的 is-a 关系来说,接口表示一种 has-a 关系,表示具有某些功能。对于接口,有一个更加形象的叫法,那就是协议(contract)。

07.更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

yccoding.com/

相关推荐
Moonbit6 分钟前
MoonBit Pearls Vol.03:01背包问题
后端·算法·编程语言
南囝coding22 分钟前
这个仓库堪称造轮子的鼻祖,建议看看!
前端·后端
JuiceFS31 分钟前
3000 台 JuiceFS Windows 客户端性能评估
后端·云原生·云计算
suedar37 分钟前
关于工程化的随想
前端
Cosolar40 分钟前
下一代 Python Web 框架?FastAPI 全面解析与实战对比
后端·python
安琪吖1 小时前
微前端:qiankun框架在开发中遇到的问题
前端·vue·element-ui
不爱说话郭德纲1 小时前
🔥产品:"这功能很常见,不用原型,参考竞品就行!" 你会怎么做
前端·产品经理·产品
wordbaby1 小时前
React 异步请求数据处理优化经验总结
前端·react.js
拉不动的猪1 小时前
回顾 pinia VS vuex
前端·vue.js·面试
Warren981 小时前
Java异常讲解
java·开发语言·前端·javascript·vue.js·ecmascript·es6