简单基类和抽象类有什么区别?

10

我正在进行一种研究,对抽象类的概念感到困惑。

就我的理解,抽象类可以包含具体方法和虚方法。它可能包含或不包含抽象方法,并且可能包含字段,限制了直接创建实例。

但其实我们可以在一个简单的基类中实现所有这些功能(增加虚方法即可,因为一个带有虚方法的基类如果没有实现并覆盖虚方法的话,就和抽象方法一样了)。那么既然接口支持多继承和事件,为什么还需要抽象类呢?


StackOverflow标签“abstract-class”和“base-class”在问题中也有很好的定义,以区分抽象类和基类,即使抽象类没有任何抽象方法。抽象类是不能被实例化的类。它们存在的目的是为多个具体类提供共同的功能和接口规范。在面向对象编程中,基类是其他类继承的类。例如,一个子类“男性”和另一个子类“女性”都可以从基类“人类”继承。 - firstpostcommenter
4个回答

22
但是我们可以通过一个简单的基类实现所有这些。
不,你不能。你不能有一个非抽象类具有抽象方法(方法的签名被定义,但没有给出实现,从而强制派生类提供实现)。
抽象类存在的原因是它们可以提供一组实现方法和抽象方法的组合。
您可以尝试避免使用抽象类,而是使用什么都不做的具体实现,但是它具有许多缺点:
- 它不会强制调用者重写实现。 - 它给人的印象是类型和特定方法正在工作,而实际上它们并没有。通过添加抽象方法和类型功能,可以防止某个人无意中使用不完整的类型。 - 它为子类提供了明确的契约,以确定他们需要提供什么功能,而基类的哪些方法正在工作,但可以选择扩展。
此外,还要考虑在没有抽象的世界中的非 void 方法。它们需要抛出(即 NotImplementedException)或返回无效值(即 null)。这可能比什么都不做的方法更糟糕。

那么一个带有虚方法的基类会是什么样子呢?忘记抽象方法的名称,它们之间有什么区别。因为虚方法也可以被视为抽象方法并进行重写。 - peter
@peter 你不能忘记它们。它们是唯一的关键区别。如果你忘记了它们,那么你就什么都没有了,但你不能忘记它们,因为它们是抽象类的重点。如果你没有任何抽象方法,那么你就不需要抽象类。 - Servy
我想表达的是虚方法,也可以称为抽象方法,它没有任何实现并且可以被覆盖。 - peter
1
@peter 这并不强制调用者覆盖实现,并给人一种类型和特定方法正在工作的印象,而实际上它们并没有。通过添加抽象方法和类型的功能,您可以防止某些人无意中使用不完整的类型,而不知道它是不完整的。它还为子类提供了明确的合同,说明他们需要提供什么功能。特别是考虑非void方法,在没有“abstract”的世界中,基本实现需要抛出或返回无效值。 - Servy
那是一个出色的评论,你能否编辑你的答案并加入这个评论呢? - peter

19
主要区别在于编译器不允许你实例化抽象类,而你可以实例化基类(这可能没有意义)。
AbstractType a = new AbstractType(); //compiler error
AbstractType d = new DerivedType(); //OK

BaseType b = new BaseType(); //OK

注意变量d,我们保证了抽象方法已被覆盖(否则DerivedType类将会有编译错误)。

由于您仍然存在许多困惑,我将给您一个例子,这个例子真正让我明白了这个概念。想象一下你正在制作一个塔防游戏。你有一个塔的类,每个塔都有攻击的能力,所以你可以像这样创建一个抽象的塔类:

abstract class Tower
{
    abstract void Attack();
}

现在我可以创建多个塔类:
class FlameTower : Tower
{
    override void Attack()
    {
        //Shoot flames at everyone
    }
}

class IceTower : Tower
{
    override void Attack()
    {
        //Shoot ice everywhere
    }
}

现在如果你想声明一个塔列表,你可以这样写:
```python towers = [] ```
这将创建一个空列表。
 List<Tower> towerList = new List<Tower>();
 towerList.Add(new FireTower());
 towerList.Add(new IceTower());

然后遍历它们并使它们全部攻击:
 foreach (Tower t in towerList)
 {
     t.Attack();
 }

每个类都保证已经实现了attack方法,因为它被标记为抽象类,如果没有实现会导致编译错误。虽然可以使用基类来完成所有这些,但是基类会允许以下情况的发生:
 towerList.Add(new Tower());

现在当它试图在那个new Tower()上调用攻击时,它将命中一个空的抽象方法,这不是我们想要的。因此,为了禁止将某些东西声明为通用塔,我们使该类为抽象类,然后我们知道每个东西都将有自己的Attack定义,并且会执行某些操作。

这里的优势是什么?我们可以间接地做,就像抽象类ab = new派生类(); - peter
@peter 但是这样你会得到一个派生类而不是抽象类(因此你可以确保抽象方法有定义)。 - Kevin DiTraglia
很棒的代码示例。它与我之前谈到的动物类相似。例如,如果 抽象类 Animal 有函数 public abstract void eatStuff(),那么每个 Animal 的具体实例都知道如何进食。 - musical_coder
3
@musical_coder:是的,我一直不喜欢用动物的例子,因为我很难想象编写动物代码,我喜欢给与制作游戏相关的例子,因为每个人都喜欢制作游戏! - Kevin DiTraglia

3

一个简单的答案可以是:

基类有它们自己的方法实现,这些实现可以在继承类中使用/添加。您可以实例化一个基类。

抽象类声明了类方法。任何继承抽象类的类都必须实现其抽象方法,否则会变成抽象类本身。您不能实例化一个抽象类。

示例:

public abstract class A
{
   public void Method1()
   {
     //method code
   }

   public abstract void Method2();

   public virtual void Method3()
   {
    //method code for class A, when class A calls Method3, this code is executed
   }
}

public class B : A
{
   public override void Method2()
   {
     //this must be implemented here to use it
   }

   public override void Method3()
   {
     //method code for class B, when class B calls Method3, this code is executed
     //or, if you choose not to override this method, the compiler will use the code in class A
   }
}

因为类B继承了类A,所以它仍然可以使用来自类A的Method1。如果类A要被抽象化,那么所有方法都将像Method2一样被声明,并且必须在类B中实现,以便可以使用。


@peter:这只是一个例子。虽然我明白你在指什么 ;) - Rahul Tripathi

3

有一些类在现实世界中并不存在,因此应该将其概念上标记为abstract。例如,考虑抽象类 Animal。在现实世界中没有“动物”这样的东西。相反,存在具体类型的动物,如DogCat等。


如果基类包含虚方法,则这将是可能的,我已经对我的问题进行了一些修改。 - peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接