抽象类 & 接口:面试中的两个反派 - 第 1 部分






4.65/5 (80投票s)
本文是系列文章“抽象类与接口:面试中的两大‘反派’”的第一部分,将解释抽象类的重要关键点。
引言
每次阅读关于技术面试的文章,总会有一个问题。抽象类和接口之间有什么区别,为什么会选择使用其中一个而不是另一个?这似乎是一个简单的问题,但大多数时候,对这个问题的回答对面试官来说似乎不够充分,并且可能会让你失去一份工作。因此,我决定写一系列关于它们的文章,并试图以我所能的最简单的方式来解释它们,同时考虑到面试官的视角。关于系列文章的标题,我的本意不是将这些概念描绘成反派。它们是 C# 中最重要的概念,没有它们我们就无法实现更好的代码效率。这个系列完全是关于它们在技术面试中的作用。如果你没有好好准备它们,可能会让你失去一份工作。所以,这些概念并非反派,但它们在面试中的作用确实如此。
我总是尝试用一个简单的方法来学习新概念:“是什么,为什么,怎么做”。
- 是什么:它是什么?
- 为什么:我为什么要学习它?
- 怎么做:我如何使用它?
我用同样的方法写了这篇文章。我将文章分为两部分。在第一部分,我将重点介绍抽象类,并在我的第二篇文章中,我将讨论接口以及两者之间的区别。
路线图
以下是题为“抽象类与接口:面试中的两大‘反派’”的系列文章的路线图
抽象类
什么是抽象类?
“abstract
”一词的字典含义是“一个没有物理实体、仅是概念上的想法或概念”。我们可以用这个想法来构建有物理实体的东西。在 MSDN 库中,“abstract
”关键字表示该事物具有缺失或不完整的实现,并且必须由其他事物来完成。
abstract
关键字可以与类、方法、属性、索引器和事件一起使用。如果我们对一个类使用 abstract
关键字,它表示该类旨在作为基类,并且可以包含 abstract
方法(想法),这些方法必须在派生类(物理实体)中实现。
抽象类是一种特殊的类,它没有实现。它不能被实例化。它的实现逻辑由派生自它的类提供。它可以包含 abstract
方法和非 abstract
方法。
抽象类中不一定只包含 abstract
方法。我们也可以有一个只包含非 abstract
方法的抽象类。
我们为什么需要抽象类?
使用抽象类,我们可以为所有派生类提供一些默认功能供其扩展。这在许多情况下有助于避免代码重复。
假设我们为 Apple 定义了一个 iPhone
类,然后将其继承给 iPhone5
和 iPhone5s
子类。实际上,我们不希望创建 iPhone
类的对象,因为我们首先需要知道 iPhone
的型号。因此,iPhone
类应该是一个抽象类,它包含一些预定义的功能,如 Call()
和 SMS()
,供所有 iPhone 型号共享。我们还可以在 iPhone 类中添加 abstract
方法,如 Model()
和 Color()
,所有继承 iPhone 的子类都必须实现这些方法。这种方法的主要优点是,每当我们把 iPhone
类继承到一个派生类(例如 iPhone5s
)时,我们就无需再次定义 Call()
和 SMS()
方法。我们只需要实现 abstract
方法即可。它有助于在所有派生类中提供默认功能,并避免代码重复。
在修改项目的情况下,抽象类也很有用。如果您计划更新项目中的基类,最好将该类设为 abstract
。因为您可以在抽象基类中定义一个功能,并且所有继承类将自动拥有相同的功能,而不会干扰层次结构。
如何定义抽象类?
正如我们前面讨论过的,可以通过在类定义前加上 abstract
关键字来声明类为 abstract
。所以,让我们开始使用一个简单的控制台应用程序来学习抽象类。
在 Visual Studio 中创建一个控制台应用程序项目,并将其命名为“AbstractClassDemo
”。
默认情况下,它会提供一个名为 Program
的类,其中包含用于代码执行的 Main
方法。我们可以通过在类定义前加上 abstract
关键字来创建一个抽象类,如下所示:
using System;
namespace AbstractClassDemo
{
abstract class iPhone { } //Definition of an Abstract Class
class Program
{
static void Main(string[] args) { }
}
}
上面的代码定义了一个简单的抽象类。但是,我们无法创建抽象类的对象/实例。它会直接报错。
using System;
namespace AbstractClassDemo
{
abstract class iPhone { } //Definition of an Abstract Class
class Program
{
static void Main(string[] args)
{
//Instantiation of an Abstract Class
iPhone iphone = new iPhone();
}
}
}
因此,我们需要在其中定义成员,这些成员可以被派生类继承。我们可以在抽象类中定义 abstract
成员和非 abstract
成员。带有非 abstract
方法的抽象类如下所示:
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
iPhone
类显示了一个非 abstract
方法 Call()
,它为所有派生自它的子类提供了默认功能。我们无法创建 iPhone
类的对象,但仍然可以在派生类中使用 Call()
方法。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
}
class Program: iPhone
{
static void Main(string[] args)
{
Console.Writeline("/////////////// - Abstract Class Demo - ///////////////");
//Instance Creation of Derived Class
Program program = new Program();
program.Call();
Console.ReadKey();
}
}
}
上面的代码显示了抽象类到具体类的简单继承。这种继承也可以由两个具体类完成。那么,我们为什么要使用抽象类呢?
答案是,提供默认功能并添加 abstract
方法。iPhone
类被所有 iPhone
型号继承,因此所有型号都需要 Call()
方法。最好在抽象类中定义 Call()
方法,以便每个派生类都能自动拥有 Call()
方法,而无需再次定义它。
每个 iPhone
型号都有其特有的功能,如 Color
和 Model
。因此,我们可以在抽象类中定义一个合同,派生类必须根据其需求来实现它。这些类型的合同被称为 abstract
方法,在本例中是 Model()
。抽象方法只有签名,没有实现。它是一种强制所有子类实现它的合同。
与抽象类一样,抽象方法也使用 abstract
关键字声明。请注意,抽象方法不能是 private
,否则会报错。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method kept as Private
abstract void Model();
}
class Program
{
static void Main(string[] args)
{
}
}
}
如果编译此代码,它会报错。
因此,声明抽象方法的正确方法如下:
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method
public abstract void Model();
}
class Program
{
static void Main(string[] args)
{
}
}
}
Model()
方法强制所有派生类实现它。我们可以定义一个名为 iPhone5s
的新具体类,它继承抽象类 iPhone
并提供 Model()
方法的定义。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method
public abstract void Model();
}
class iPhone5s: iPhone
{
}
class Program
{
static void Main(string[] args)
{
}
}
}
如果我们不在派生类中提供抽象方法的定义,它将抛出错误。
好的。让我们在派生类中提供 Model()
方法的定义。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method
public abstract void Model();
}
class iPhone5s: iPhone
{
//Abstract Method Implementation
public void Model()
{
Console.WriteLine("Model: The model of this iPhone is iPhone5s");
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
现在我们在 iPhone5s
类中定义了 Model()
方法。让我们编译上面的代码。哇,它给了我们一个错误和一个警告。
错误信息是“派生类中未实现 Model()
方法”。这似乎是合理的,因为我们没有覆盖基类方法,这意味着编译器认为派生类中没有 Model()
方法的实现。
它还给了我们一个警告:“要使当前成员覆盖该实现,请添加 override
关键字,否则添加 new
关键字”。这意味着编译器对我们在 iPhone5s
类中声明的 Model()
方法感到困惑。
如果您想在派生类中覆盖基类方法,请使用 override
关键字与方法一起使用;如果派生类方法与基类方法没有任何关系,则使用 new
关键字。new
关键字表示派生类中的方法与基类方法无关。
在我们的例子中,我们希望在派生类中定义基类方法。所以,我们使用 override
关键字。此外,我们还可以在 iPhone5s
类中添加本地方法。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method
public abstract void Model();
}
class iPhone5s: iPhone
{
//Abstract Method Implementation
public override void Model()
{
Console.WriteLine("Model: The model of this iPhone is iPhone5s");
}
//Derived Class Local Method
public void LaunchDate()
{
Console.WriteLine("Launch Date: This iPhone was launched on 20-September-2013");
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
一切顺利。让我们使用 iPhone5s
类,该类现在同时具有来自抽象类的成员和它自己的成员。
using System;
namespace AbstractClassDemo
{
abstract class iPhone
{
//Non-Abstract Method
public void Call()
{
Console.WriteLine("Call Method: This method provides Calling features");
}
//Abstract Method
public abstract void Model();
}
class iPhone5s: iPhone
{
//Abstract Method Implementation
public override void Model()
{
Console.WriteLine("Model: The model of this iPhone is iPhone5s");
}
//Derived Class Local Method
public void LaunchDate()
{
Console.WriteLine("Launch Date: This iPhone was launched on 20-September-2013");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("/////////////// - Abstract Class Demo - ///////////////");
Console.WriteLine("iPhone 5s:");
iPhone5s iphone5s = new iPhone5s();
iPhone5s.Call();
iPhone5s.Model();
iPhone5s.LaunchDate();
Console.ReadKey();
}
}
}
如果我们运行代码,它会完美运行。
在前面的示例中,我以一种非常简单的方式解释了如何使用抽象类。我们能够将抽象类及其抽象成员实现到具体类中。以下是使用抽象类时需要记住的一些要点:
要点
- 我们无法创建抽象类的对象,但可以创建它的引用。
using System; namespace AbstractClassDemo { abstract class absClass{ } class Program { static void Main(string[] args) { //We can't do this //absClass cls = new absClass(); //We can do this absClass cls; } } }
- 抽象类到抽象类的继承是可能的。我们不需要在派生抽象类中实现基抽象类的抽象方法。我们可以在稍后在具体类中实现它们。
using System; namespace AbstractClassDemo { abstract class absClassA { //Abstract Method public abstract void SomeMethod(); } abstract class absClassB: absClassA //Abstract to Abstract Inheritance { } class Program: absClassB { public override void SomeMethod() { //Some Implementation Here } public static void Main(string[] args) { } } }
- 抽象类永远不能是
sealed
或static
。如果编译下面的代码,它会报错。using System; namespace AbstractClassDemo { sealed abstract class absClassA { } class Program { public static void Main(string[] args) { } } }
- 抽象类可以包含
abstract
和非abstract
方法。 abstract
关键字可以与类、方法、属性、索引器和事件一起使用。- 抽象成员只能在抽象类中声明。
- 抽象成员不能是
static
或private
。 - 抽象方法不能被标记为
virtual
。 - 具体类不能继承多个抽象类,换句话说,不支持多重继承。
- 没有抽象类,我们就无法实现模板方法模式。
结论
我希望本文能帮助您理解抽象类的各种可能性。在我的文章的第二部分中,我将讨论接口。您的反馈和建设性意见始终受到赞赏,请继续提出。在那之前,努力在宇宙中留下痕迹吧!