面向对象编程:理解抽象化

4
我已经阅读了许多文章和一些Stack Overflow的问题,以更好地理解“抽象”概念,但我有点困惑。这是我正在阅读的内容:这里 引用如下:
抽象是“代表本质特征而不代表背景细节”。抽象使您关注对象所做的事情而不是它如何做到的。
给出的代码:
abstract class MobilePhone
}
    public void Calling();
    public void SendSMS();
}

public class Nokia1400 : MobilePhone
{

}

public class Nokia2700 : MobilePhone
{
    public void FMRadio();
    public void MP3();
    public void Camera();
}

我的问题是,当我们继承抽象类时,难道我们不需要在子类中实现细节吗?
在超类型中,calling()和sendSms()没有任何实现,所以当我们在子类中实现它时,我们也应该知道背景细节。那么在这个例子中,抽象实际上是如何工作的呢?
编辑:如果您来这里寻求帮助,这些人给出了最好的答案:Complexity、Sergey Berezovskiy。

1
在你的例子中,它简单地说明了在每个手机中,调用和发送短信是MobilePhone的核心功能,因此当你的手机更换时,你不需要编写一个用于呼叫和发送短信的方法,你可以直接从抽象类中使用它,而且没有人可以更改它,因为抽象类不允许创建对象。 - Dhaval Patel
@DhavalPatel,这两个方法中会提供实现细节,对吧? - Sikander
是的,您必须在抽象类中实现这两个方法,因为如果您使用非抽象方法,则必须进行实现。基本上,抽象类可消除代码冗余,无需一遍又一遍地编写代码。 - Dhaval Patel
7个回答

4

首先,你的代码存在几个问题。

  • 抽象类包含没有实现的方法并且不是抽象的,这是无效的。你应该将这些方法声明为抽象的,或者在其中提供具体实现。如果你想要一个具体实现但同时又能够重写该方法,则必须使用虚关键字。
  • 所以这是你的抽象类的选择:

没有具体实现的方法:

abstract class MobilePhone
{
    public abstract void Calling();
    public abstract void SendSMS();
}

可以被重写的带有方法体的方法:

abstract class MobilePhone
{
    public virtual void Calling()
    {
        // Code goes here.
    }

    public virtual void SendSMS()
    {
        // Code goes here.
    }
}

无法被覆盖的有主体方法:

abstract class MobilePhone
{
    public void Calling()
    {
        // Code goes here.
    }

    public void SendSMS()
    {
        // Code goes here.
    }
}

然后还有几个问题。 您的抽象类没有提供构造函数,这使得对象默认为私有,因此您的Nokia1400和Nokia2700类不能从基类继承,因为基类比抽象类更不可访问。
现在,根据您创建抽象类的方式,实现它的类必须满足一些要求:
  • 当基类包含抽象成员时,它应该在继承类中实现。
  • 当基类包含虚拟成员时,它不应在继承类中实现,但如果需要,可以覆盖它。
此外,你的最后一个类也无法编译,因为它包含没有实体的方法。这些方法只有在它们是抽象的并且必须在抽象类中时才被允许。
以下是基于您的代码的示例:
public abstract class MobilePhone
{
    public virtual void Calling()
    {
        Console.Write("Calling");
    }

    public abstract void SendSMS();
}

public class Nokia1400 : MobilePhone
{
    public override void SendSMS()
    {
        Console.WriteLine("Sending SMS from Nokia 1400.");
    }
}

public class Nokia2700 : MobilePhone
{
    public void FMRadio()
    {
        Console.WriteLine("FM Radio");
    }

    public void MP3()
    {
        Console.Write("MP3");
    }

    public void Camera()
    {
        Console.WriteLine("Camera");
    }

    public override void SendSMS()
    {
        Console.WriteLine("Sending SMS from Nokia 2700.");
    }
}

因此,这是一篇相当长的文章,但是我希望它能有所帮助。


这是一个很好的解释,如果您有时间,也可以参考这篇 CodeProject 文章中的这些更正:http://www.codeproject.com/Articles/600449/IntroductionplustoplusObjectplusOrientedplusProgra - Sikander
我是说你提出了非常好的观点(因为你指出了一些存在于代码中的问题,使我清楚了整个思路),你也可以建议将这些观点放在我从上面提到的链接获取的文章中。你的回答比那篇文章更有帮助,它可能会帮助将来阅读那篇文章的其他人。 - Sikander
我会尽我所能,因为我甚至不知道我是否可以建议对CodeProject上的文章进行编辑,并且我也没有账户。如果这篇文章帮助您理解了主题,请考虑将其标记为问题的答案。 - Complexity

2
你可以拥有抽象方法和虚方法
抽象方法必须由扩展类实现,没有函数体。
虚方法有函数体但不需要被覆盖。如果它们没有被覆盖,则执行基类的虚方法体。
有一些注意事项,但这就是主要思想。
public abstract class MyBase
{
    public abstract void MethodMustBeImplemented();
    public virtual void DoesNotHaveToBeOverwritten()
    {
      //Do WORk
    }
}


public class Implementor: MyBase
{

}

这将会抛出一个编译时错误,因为MethodMustBeImplemented()没有被重写。

但是下面的代码是正确的:

public class Implementor: MyBase
{
    public override void MethodMustBeImplemented()
    {
      //Do WORk
    }
}

请确认当我们将类标记为抽象时,是否需要实现所有抽象/非抽象方法。 - Sikander
1
在C#中,只有那些标记为“abstract”的方法需要被重写。 - David Pilkington

2
让我们为您的报价添加缺失的细节:
抽象表示客户端代码所需的基本特征,而不表示背景细节。使用抽象时,它让您专注于对象执行的操作,而不是它如何执行操作(因为您不知道哪个类实现它)。
我相信现在很清楚了。您应该从客户端的角度来看待抽象,即从将使用您的MobilePhone的代码的角度来看待。抽象不一定是抽象类(一个有点误导性的术语),它也可以是接口。
现实世界的例子-当您充当电视机的客户端时,您使用电视机的抽象(应该有不同的频道、音量控制等),但您不关心这些抽象的确切实现是什么(如果您需要的所有电视抽象功能都得到了实现)。客户应该满意,并且他应该远离细节。我打赌你只是看电视,而不是看某个特定的Toshiba NX-42C?

1
缺失的细节非常有帮助...! - Sikander

2
你的困惑来自于你需要区分以下两种概念: - 抽象类和抽象方法 - 抽象类无法被实例化,必须由另一个类继承后才能实例化。 现在回到抽象类:它可以有两种类型的方法: - 普通方法(例如你的示例中的calling和sendsms)。因为这些是普通方法,所以它们被认为是实现了空的方法体,因此继承类不需要实现它们[这就是让你困惑的地方]。 - 抽象方法:这些方法前缀带有关键字“abstract”,一旦出现关键字“abstract”,则继承类应该实现它们。
更进一步解释一下: 如果方法calling()和sendingSms()带有“abstract”前缀,则必须实现它们。
现在虚方法和重写是另一个概念,如果你还感兴趣,我可以继续讲解。

1
abstract class C{
public abstract void met1();
public void met2(){
    System.out.println("Hello");
}
}

在抽象类中,如果您扩展了抽象类,则必须实现抽象类的无主体方法,而在抽象类中具有实现的方法可能会在子类中被覆盖或不被覆盖。因此,在上述类中,如果您扩展了Class C,则必须在您的类中放置met1的实现,而met2是可选的,您甚至可以覆盖它(如果您想这样做)。
@Override
public void met2(){
    super.met2();
}

抽象化实际上是将子类的实际实现隐藏在专注于超类的观察者视野之外。
例如,考虑抽象类Dog,其中有像bark()这样的方法,但是扩展Dog的类将拥有它们自己的实现,例如你有一个类BullDog,它扩展了Dog。

那么您的意思是这两个方法 public void Calling(); public void SendSMS(); 的实现细节将在抽象类中提供,并且这些实现将在子类中使用? - Sikander
在抽象类中没有声明(Body Less),但是在子类中必须有实现。 - akash

1
根据我的回忆,实现一个抽象类就是填写其中的方法体。
这就像签订一份合同。
当你使用这个抽象类并实现它时,你同意拥有那些方法签名(方法名称、参数和返回类型)以及那些变量。
好处在于,如果一个类实现了它,你就知道它(实现了该抽象类的类)可以按照那种方式进行通信(它具有那些方法名称和变量)。因此,抽象类就像 API 的基础。

1
请注意,抽象类可以具有实现细节(甚至根本没有抽象方法,只是声明为抽象以防止实例化),因此将抽象类称为“API的基础”并不完全准确。更准确的描述应该用于接口,因为它们不打算具有任何实现细节。 - user3613916
@user3613916 啊,没错,那时我只用过接口,而且已经十多年了。我现在想起来抽象类也很相似,不过我几乎没用过。谢谢指正。 - barlop

1
由于基类被声明为抽象类,因此无法实例化,因此您无法从基类调用这些抽象方法,只能从实现了提到的抽象类的派生类中调用。
当您将类声明为抽象类时,您表明该类无法实例化。然后,您可以在类内部声明抽象方法,因为这些方法无论如何都不能被调用,所以您不需要为它们提供实现。但是,任何继承自您的基类的非抽象类都必须实现这些方法,以允许实例化。

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