为什么我们需要抽象方法?

Why do we need abstract methods?

我最近一直在研究抽象方法,我不明白为什么我们需要它们? 我的意思是,毕竟,我们只是凌驾于他们之上。你知道它只是一个声明吗?为什么我们需要它们?

此外,我尝试从互联网上了解这一点,到处都有这样的解释想象有一个抽象的 class 人类然后有它的子classes disablednot disabled 那么 human class walking() 中的抽象函数将包含不同的主体或代码。现在我要说的是,为什么我们不在禁用和未禁用的 subclasses 中创建一个函数而不是覆盖。因此再次回到第一段中的问题。请解释一下。

基类型中的抽象方法定义是一种契约,保证该类型的每个具体实现都将具有该方法的实现。

没有它,编译器将不允许您在基类型的引用上调用该方法,因为它不能保证这样的方法将始终存在。

所以如果你有

MyBaseClass x = getAnInstance();
x.doTheThing();

MyBaseClass没有方法,那么编译器会告诉你它不能让你那样做。通过添加一个 abstract doTheThing 方法,你可以保证 getAnInstance() 可以 return 的每个具体实现都有一个实现,这对编译器来说已经足够好了,所以它会让你调用那个方法。

基本上一个更基本的真理,首先需要理解的是:

在某些情况下,变量的类型比它所持有的值的类型更通用。在简单的情况下,您可以将变量设为特定类型:

MyDerivedClassA a = new MyDerivcedClassA();

在那种情况下,您显然可以调用 MyDerivedClassA 的任何方法,并且不需要基 class.

中的任何抽象方法

但有时您想使用 any MyBaseClass 实例做某事,而您 不知道 什么具体类型它是:

public void doTheThingsForAll(Collection<? extends MyBaseClass> baseClassReferences) {
  for (MyBaseClass myBaseReference : baseClassReferences) {
    myBaseReference.doTheThing();
  }
}

如果您的 MyBaseClass 没有 doTheThing 抽象方法,那么编译器不会让您这样做。

继续你的例子,在某些时候你可能有一个人类列表,你并不关心他们是否残疾,你关心的只是你想调用 walking()他们的方法。为了做到这一点,Human class 需要定义一个 walking() 方法。但是,如果不知道人是否残疾,您可能不知道如何实施。因此,您将实现留给继承 classes.

抽象方法最明显的用途之一是让抽象 class 从其他方法的实现中调用它们。

这是一个例子:

class AbstractToy {
    protected abstract String getName();
    protected abstract String getSize();
    public String getDescription() {
        return "This is a really "+getSize()+" "+getName();
    }
}
class ToyBear extends AbstractToy {
    protected override String getName() { return "bear"; }
    protected override String getSize() { return "big"; }
}
class ToyPenguin extends AbstractToy {
    protected override String getName() { return "penguin"; }
    protected override String getSize() { return "tiny"; }
}

请注意 AbstractToygetDescription 实现如何能够调用 getNamegetSize,即使定义在子 class 中es。这是一个名为 Template Method.

的著名设计模式的实例

在其他答案中有一些如何使用它的例子,所以让我解释一下为什么你可能会这样做。

首先,面向对象设计的一个常见规则是,一般来说,您应该尝试针对接口而不是特定实现进行编程。如果您以后需要更改某些行为,这往往会提高程序的灵活性和可维护性。例如,在我编写的一个程序中,我们将数据写入 CSV 文件。我们后来决定改为写入 Excel 文件。对接口(而不是特定实现)进行编程使我们更容易进行此更改,因为我们可以 "drop in" 一个新的 class 来写入 Excel 文件来代替class 写入 CSV 文件。

你可能还没有研究过这个,但这对某些人来说其实很重要design patterns. A few notable examples of where this is potentially helpful are the Factory Pattern, the Strategy Pattern, and the State Pattern

就上下文而言,设计模式是描述和记录已知问题解决方案的标准方式。例如,如果有人说 "you should use the strategy pattern to solve this problem," 这让您应该如何解决问题的总体思路变得清晰。

因为有时我们需要一个在其实例中表现不同的方法。

例如,假设一个 class Animal 包含一个方法 Shout。 我们将有此 Animal class 的不同实例,但在某些情况下我们需要以不同方式实现该方法,如下所示:

class Animal:
    /**
       different properties and methods
       which are shared between all animals here
    */
    ...

    method shout():
        pass

class Dog extends Animal:
    method shout():
        makeDogVoice()

class Cat extends Animal:
    method shout():
        makeCatVoice()


dog = new Animal
cat = new Animal

dog.shout()
cat.shout()

原来狗叫狗叫,猫叫猫叫!没有实现共享行为两次

在这些情况下有不同的喊叫行为。所以我们需要摘要classes.

假设您不知道实现,但仍想声明一个方法,那么我们可以借助抽象修饰符并将其设为抽象方法。对于抽象方法,只有声明可用,而没有实现。因此它们应该以 ;

结尾

示例:

public abstract void m1(); // this is correct 
public abstract void m1(){  ...  } // this is wrong

优点: 通过在父级 class 中声明抽象方法,我们可以为子级 class 提供指导,以便强制执行哪些方法。

示例:

abstract class Vehicle{
  abstract int getNoOfWheels();
}

Class Bus extends Car{
  public int getNoOfWheels(){
     return 4;
  }
}

如果你想要简短的答案,想想这个:

你有一个摘要classCar

您实现了 2 个 class 扩展它,FerrariMercedes

现在: 如果您对所有汽车通用的方法 drive() 执行以下操作之一会怎样:

1) 改变了方法的可见性,

2) 将方法的名称从 driving 更改为 Driving,

3) 将 return 类型从 boolean 更改为 int

想一想。它似乎没有任何区别,因为它们是不同的实现?

错了!

如果我遍历一组汽车,我将不得不为每种类型的汽车调用不同的方法,从而使抽象的实现变得无用。

摘要 classes 将 classes 与一个共同的模板分组,共享共同的属性。这有帮助的一种方法是遍历数组: 抽象方法确保所有汽车都声明相同的方法, 因此,Car 的子 class 的任何对象都将具有方法 drive(),如摘要 class 中所定义,使得提到的 for 循环易于实现。

希望对您有所帮助。