为什么要将一个类声明为抽象类?


40

我知道应用于抽象类的语法,规则,并且想知道抽象类的用法

抽象类不能直接实例化,但可以被其他类扩展

这样做的好处是什么?

它与接口有何不同?

我知道一个类可以实现多个接口,但只能扩展一个抽象类。接口和抽象类之间的唯一区别是吗?

我知道接口的用法。我从Java中AWT的事件委托模型中学到了这一点。

在哪种情况下我应该将类声明为抽象类?那有什么好处?


14
您已经知道了这个问题。搜索将出现其他类似问题。您应该从Google开始,这将导致您进入Stack Overflow。所有这些都是重复的:stackoverflow.com/search?q=abstract+interface
S.Lott,

1
“他们只讨论...规则的抽象类的用法”。什么?“规则”和“用法”有什么区别?顺便问您确切的问题。我知道。我回答了。继续寻找。了解如何使用搜索很重要。
S.Lott

我的意思是“在什么情况下我应该将类声明为抽象类?这有什么好处?”
Vaibhav Jani,

接口是一个纯粹的抽象类,仅此而已。他们是一样的。我一直在对无法实现的基类中的函数使用抽象类,因为它只需要子类具有的数据,但是我想确保每个子类都具有此函数并相应地实现。
AngryBird

我发现模板方法模式是一种非常强大的模式,并且是抽象类的良好用例。
m3th0dman

Answers:


49

这个答案很好地解释了抽象类和接口之间的区别,但是没有回答为什么要声明一个。

从纯技术的角度来看,从来就没有要求声明一个类为抽象。

考虑以下三个类:

class Database { 
    public String[] getTableNames() { return null; } //or throw an exception? who knows...
}

class SqlDatabase extends Database { } //TODO: override getTableNames

class OracleDatabase extends Database { }  //TODO: override getTableNames

你不具备做数据库抽象类,即使有,其实现一个明显的问题:当你写这个程序,你可以输入new Database(),这将是有效的,但它永远不会成功。

无论如何,您仍然会得到多态性,因此,只要您的程序仅创建SqlDatabaseOracleDatabase实例,您就可以编写如下方法:

public void printTableNames(Database database) {
    String[] names = database.getTableNames();
}

抽象类通过阻止开发人员实例化基类来改善这种情况,因为开发人员已将其标记为缺少功能。它还提供了编译时的安全性,因此您可以确保扩展抽象类的所有类都提供最低限度的功能,并且您不必担心放置继承者以某种方式拥有的存根方法(如上述方法)神奇地知道他们必须重写一个方法才能使它起作用。

接口是一个完全独立的主题。使用界面可以描述可以对对象执行的操作。在编写使用其他组件,对象的服务的方法,组件等时,通常会使用接口,但是您不在乎从中获取服务的对象的实际类型是什么。

请考虑以下方法:

public void saveToDatabase(IProductDatabase database) {
     database.addProduct(this.getName(), this.getPrice());
}

您不必在乎database对象是否从任何特定对象继承,而只在乎它是否具有addProduct方法。因此,在这种情况下,接口比使所有类都继承自同一基类更合适。

有时两者的结合非常好。例如:

abstract class RemoteDatabase implements IProductDatabase { 
    public abstract String[] connect();
    public abstract void writeRow(string col1, string col2);

    public void addProduct(String name, Double price) {
        connect();
        writeRow(name, price.toString());
    }
}

class SqlDatabase extends RemoteDatabase {
    //TODO override connect and writeRow
}

class OracleDatabase extends RemoteDatabase { 
    //TODO override connect and writeRow
}

class FileDatabase implements IProductDatabase {
    public void addProduct(String name, Double price) {
         //TODO: just write to file
    }
}

请注意,某些数据库如何从RemoteDatabase继承以共享某些功能(例如在写一行之前进行连接),但是FileDatabase是仅实现的单独类IProductDatabase


16

相似之处

抽象类和接口是抽象所必需的。不能使用new实例化它们,但是可以将其解析为反转控制容器或通过工厂模式。

区别

  1. 介面

    • 定义众所周知的公共合同,能力类型
    • 适用于显示水平继承,即在继承的第一层进行分支(例如,通过ILog定义到数据库,文本文件,XML,SOAP等的日志记录工具)
    • 所有成员都是公开的
    • 不允许执行
    • 继承子项可以实现许多接口
    • 对第三方集成有用
    • 命名通常从I开始
  2. 抽象类

    • 定义结构,身份和一些默认的受支持行为
    • 适用于显示垂直继承,即在多个级别上进行深度分支(例如,域驱动开发中的AbstractEntity类)
    • 成员可以具有不同的知名度(从公共到私人)
    • 您可以实现一些成员(例如* Reader类)
    • 继承子项只能有一个基本抽象类

通过简单的Google查询实际上很容易找到答案。


您对实施的偏执没有什么看法?java类可以实现接口。
Sajuuk

@Sajuuk该行引用一个接口。您不能将合同的实现放入接口中。您可能在抽象类中具有合同的默认实现。
oleksii

11

它与接口有何不同?

在抽象类中,您可以实现一些方法,其余(强制)其余部分由扩展类来实现。您不能在接口中实现方法。扩展普通类时,您不能强迫任何人重写任何内容。使用抽象类,您可以。


这确实需要Java 8的更新,其中引入了默认方法。
HaakonLøtveit'5

8

抽象类用于“是”关系,而接口则用于“可以”。

抽象类使您可以添加基本行为,从而使程序员不必编写所有代码,而仍然可以迫使他们遵循您的设计。


3

除了深入的技术细节(例如抽象类的某些方法的实现等)之外,含义如下:

接口定义了通用功能-IEnumerable定义了可以枚举实现此接口的类。它没有对类本身说任何话。

抽象(或基类)类定义行为-WebRequest定义所有子类(如HttpWebRequest等)的常见行为。它定义类的核心含义及其真正目的-访问Web资源。


2

维基百科条目

接口和抽象类之间的主要区别是抽象类可以提供实现的方法。使用接口,您只能声明方法,并编写其签名。这是一个类的示例,该类扩展了实现两个接口的抽象类:(java)

interface MyInterface1 {
  string getValue1();
}

interface MyInterface2 {
  string getValue2();
}

abstract class MyAbstractClass implements MyInterface1, MyInterface2{
  void printValues() {
    System.out.println("Value 1: " + getValue1() + ", Value 2: " + getValue2() + 
                       ", Value 3: " + getValue3());
  }

  protected abstract string getValue3();
}

class ImpClass extends MyAbstractClass {
  public string getValue1() {
    return "1";
  }

  public string getValue2() {
    return "2";
  }

  protected string getValue3() {
    return "3";
  }
}

在此示例中,MyAbstractClass提供了打印所有三个值的公共方法。在ImpClass中,您需要分别从MyInterface1MyInterface2实现getValue1和getValue2,并从抽象类实现getValue3 。

Voilà。

还有更多方面(接口:仅公共方法,抽象类:受保护的抽象和公共抽象方法),但是您可以自己阅读。

最后,仅提供抽象方法的抽象类是“纯”抽象基类,也就是接口。


2
  • 接口-当几个类共享一个API时(方法名称和参数)
  • 抽象类-当几个类共享相同的代码时(实现)

换句话说,您应该从一个问题开始:“这些类是否必须共享实现,或者它们只是具有公共接口?”

如果答案是混合的,例如-这三个类必须共享实现,而其他两个类仅共享其API-那么您可以为所有这五个类创建接口,并为这三个类创建抽象类,码。

还有其他共享实现的方式,例如,用该实现封装对象(例如,以策略模式)。


1

当您不希望允许开发人员(可能是您自己)实例化它时,可以声明一个类抽象,因为它行不通或没有意义。

例如,考虑存在不同类型游戏实体的游戏。它们都继承自基GameEntity类。

abstract class GameEntity{

    int lifePoint, speed, damage;

    public attack(GameEntity target){ target.damage(damage); }

    public damage(int damageInflicted){ lifePoint -= damageInflicted - speed; }

    // etc...

}

声明该类是abstract因为实例化它没有意义。它声明了游戏实体的一些动作和某些属性,但是这些属性在此类中没有初始化。此类用作游戏实体的模板,但并不意味着可以单独实例化,也可以这样声明abstract

关于抽象类和接口在用法上的区别:

如我所见,接口是一种获得多态行为而不受某些语言的单一继承机制限制的方法。

让我们以游戏为例。考虑Enemy从派生的类GameEntity。此类有一个方法attackMeFromDistance(RangedAttacker attacker)。此方法旨在允许实体从远处攻击敌人。

如您所见,此方法将RangedAttacker类型作为参数。但是,所有游戏实体都已经继承自GameEntity。他们无法扩展其他课程。

就拿类MageArcher的例子。我们希望允许它们都被接受为attackMeFromDistance(RangedAttacker attacker)方法中的参数,但它们已经从派生GameEntity

为了解决这个问题,我们创建一个新接口:

interface RangedAttacker{
    public void attackFromDistance();
}

实现此接口的类必须实现该attackFromDistance()方法,因此可以确保它具有远程攻击能力。这意味着该attackMeFromDistance方法现在可以安全地接受实现此接口的类。因此,创建MageArcher实现该接口可以解决我们的问题。

对我来说,这就是界面的力量。

综上所述,当您想为某些类提供基类时,通常会使用抽象类,但abstract单独实例化它(或在具有方法的情况下,必须将其实例化)是没有意义的。由子类实现,在这种情况下,编译器将强制您创建类abstract。您将使用接口来获得多态行为,而不受单一继承机制的限制。


0
  1. 对于某些类(业务逻辑)而言,很少有通用的方法。其余方法不同。在这种情况下,您可以在一个类中实现所有通用方法,然后将其余方法声明为抽象方法。然后,您应该将该类声明为抽象。
  2. 有时您不允许直接向类创建对象。您需要将该类声明为抽象类。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.