65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (80投票s)

2014年9月11日

CPOL

8分钟阅读

viewsIcon

100879

本文是系列文章“抽象类与接口:面试中的两大‘反派’”的第一部分,将解释抽象类的重要关键点。

引言

每次阅读关于技术面试的文章,总会有一个问题。抽象类和接口之间有什么区别,为什么会选择使用其中一个而不是另一个?这似乎是一个简单的问题,但大多数时候,对这个问题的回答对面试官来说似乎不够充分,并且可能会让你失去一份工作。因此,我决定写一系列关于它们的文章,并试图以我所能的最简单的方式来解释它们,同时考虑到面试官的视角。关于系列文章的标题,我的本意不是将这些概念描绘成反派。它们是 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();
       }
   }
}

如果我们运行代码,它会完美运行。

在前面的示例中,我以一种非常简单的方式解释了如何使用抽象类。我们能够将抽象类及其抽象成员实现到具体类中。以下是使用抽象类时需要记住的一些要点:

要点

  1. 我们无法创建抽象类的对象,但可以创建它的引用。
    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; 
           }
       }
    }
  2. 抽象类到抽象类的继承是可能的。我们不需要在派生抽象类中实现基抽象类的抽象方法。我们可以在稍后在具体类中实现它们。
    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) 
           {
           }
       }
    }
  3. 抽象类永远不能是 sealed static。如果编译下面的代码,它会报错。
    using System;
    
    namespace AbstractClassDemo
    {
       sealed abstract class absClassA
       { 
       }   
    
       class Program
       {
           public static void Main(string[] args) 
           {
           }
       }
    }

  4. 抽象类可以包含 abstract 和非 abstract 方法。
  5. abstract 关键字可以与类、方法、属性、索引器和事件一起使用。
  6. 抽象成员只能在抽象类中声明。
  7. 抽象成员不能是 static private
  8. 抽象方法不能被标记为 virtual
  9. 具体类不能继承多个抽象类,换句话说,不支持多重继承。
  10. 没有抽象类,我们就无法实现模板方法模式。

结论

我希望本文能帮助您理解抽象类的各种可能性。在我的文章的第二部分中,我将讨论接口。您的反馈和建设性意见始终受到赞赏,请继续提出。在那之前,努力在宇宙中留下痕迹吧!

© . All rights reserved.