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

抽象类和接口:面试中的两个“大魔王”——第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (44投票s)

2014 年 9 月 24 日

CPOL

12分钟阅读

viewsIcon

68993

本文是系列文章“抽象类与接口:面试中的两个‘恶魔’”的第二部分,重点讲解接口的重要知识点。

引言

在我第一篇文章中,我收到了很多反馈,有积极的,有消极的,也有中立的,但每个人都问的一个问题是:为什么我暗示它们是“恶魔”?我只是想澄清一下,我无意将它们描绘成恶魔。这些是编程中最重要的概念,没有它们,我们就无法获得更好的代码效率。

通常在每次面试中,第一个问题是:“抽象类和接口之间有什么区别?”而你的整个面试都取决于对这个问题的回答。“第一印象是最后的印象”。所以这些概念不是恶魔,但它们在面试中的作用确实如此。关于批评的声音就到这里,让我们继续这个系列的下一部分:接口

接口是软件项目最重要的组成部分,没有它,项目就不完整。很多人可能不会同意我的看法并进行辩论。是的,是否使用接口是一个有争议的问题。如果使用,那么如何使用?在这篇文章中,我将解释接口的重要关键方面。就像我上一篇关于抽象类的文章一样,我在这篇文章中采用了“是什么、为什么和如何”的方法,并试图用最简单的方式来解释它。

路线图

如果您是第一次阅读本文,我请求您先阅读本系列的第一部分,然后再返回阅读本文。以下是题为“抽象类与接口:面试中的两个‘恶魔’”的系列文章的路线图:

接口

什么是接口?

在现实世界中,接口意味着一种与某物交互的媒介。确切地说,它是两个系统、主体、组织相遇并交互的点。交互需要遵循一些规则。假设您正在参加程序员职位的面试。只有当面试官和你讲同一种语言时,面试才可能进行。此外,您和面试官拥有相同的编程语言技能集可以讨论。

同样,在编程世界中,接口意味着一个用于与多个代码模块交互的约定。如果一个想与一个接口通信,它必须实现该接口并定义其成员。把它想象成面试官的问题,如果你想得到这份工作,你需要正确地回答它。

MSDN 库将接口定义为纯粹的抽象类。接口只包含方法、属性、事件或索引器的签名。它本身没有任何实现,只能由类或结构实现。任何实现接口的两个者都必须提供接口中指定的成员的定义。它就像一个供所有派生类遵守的约定。

接口使用关键字“interface”声明。接口成员隐式地是publicabstract的,因此我们不能为其添加任何访问修饰符。接口不能包含字段、常量成员、构造函数、析构函数和static成员。

我们为什么需要接口?

接口不是类。它只包含方法签名。它本身没有任何实现,也不能被实例化。其实现逻辑由派生自它的类提供。接口通常被认为是纯粹的抽象类。然而,与抽象类相比,使用接口的一个优点是“多重继承支持”。在 C# 中,同一个派生类不能继承两个类(抽象类或具体类)。如果两个类具有相同的方法签名,这会在派生类中引起歧义。我们可以使用接口在 C# 中实现多重继承。

接口在面向服务架构 (SOA) 中起着至关重要的作用。在 WCF 中,我们使用接口来定义服务契约。一个类也可以用来定义服务契约,而不是接口,但我们无法通过类实现更好的功能。使用接口,单个类可以实现任意数量的服务契约接口。通常,使用接口作为服务契约而不是实际类被认为是最佳实践。

大多数设计模式和原则都基于接口而不是类继承。一些例子是建造者设计模式、工厂模式、接口隔离原则等等。

如何定义接口?

假设我们需要定义一个智能手机类。该类可以包含操作系统应用商店通话等成员。智能手机可以是基于 Android 的,也可以是基于 iOS 的,但不能同时是两者。Android 和 iOS 智能手机之间没有共同的功能,所以我们不需要提供任何默认功能。一种方法是将Smartphone类设为abstract,并将其所有成员也设为abstract。这种方法效果很好,并且像SamsungAppleHTC这样的具体类可以从中继承。

现在,几天后,Apple想为其智能手机添加 Touch ID 功能。我们可以将TouchID作为SmartPhone抽象基类中的一个抽象方法。但是,如果HTCSamsung都不想要这个功能呢?所以,TouchID方法不能放在SmartPhone抽象类中。另一种方法是定义另一个抽象Features,并将TouchID方法添加到其中。这也糟糕,因为 C# 不支持将多个类(抽象或具体)继承到派生类中。

在这种情况下,接口很有用,并在解决问题中起着至关重要的作用。接口只提供方法定义,就像抽象类一样,但可用于多重继承。我们可以将Features类变成一个接口,并将TouchID方法添加到其中。它只提供方法签名,继承它的任何类都可以以自己的方式实现它。一个类继承多个接口在 C# 中也是完全有效的。同样,我们可以将SmartPhone类设为接口而不是抽象类。与创建纯粹的抽象类相比,使用接口会更好。

注意:这个例子不是最好的,但我认为它能说明问题。它只是为了理解接口。

让我们考虑上面讨论的例子,并创建一个控制台应用程序。打开 Visual Studio 并添加一个名为“InterfaceDemo”的新控制台项目。

默认情况下,它会提供一个名为Program的类,其中包含一个用于代码执行的Main方法。让我们创建一个抽象SmartPhone,并在其中定义OSAppStore抽象方法。我们可以通过在类定义前加上“abstract”关键字来创建抽象类。如果您不熟悉抽象类,请参阅本系列第一部分

using System;

namespace InterfaceDemo
{
    //Abstract Class SmartPhone with only abstract methods in it    
    abstract class SmartPhone
    {
        public abstract void OS();
        public abstract void AppStore();
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

现在定义继承自SmartPhone的具体类AppleSamsung,并为OSAppStore抽象方法提供定义。

using System;

namespace InterfaceDemo
{
    //Abstract Class SmartPhone with only abstract methods in it    
    abstract class SmartPhone
    {
        public abstract void OS();
        public abstract void AppStore();
    }

    class Apple : SmartPhone
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }
    }

    class Samsung : SmartPhone
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

如果我们现在编译代码,它会正常工作。我们的SmartPhone类由AppleSamsung这两个不同的具体类实现,并根据它们进行定义。现在,假设Apple想提供 Touch ID 功能给它的智能手机。我们可以在SmartPhone类中添加另一个抽象方法TouchID,然后让Apple继承并实现它。

using System;

namespace InterfaceDemo
{
    //Abstract Class SmartPhone   
    abstract class SmartPhone
    {
        public abstract void OS();
        public abstract void AppStore();

        //TouchID method meant only for Apple Class   
        public abstract void TouchID();
    }

    class Apple : SmartPhone
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }

        //Implementing the TouchID feature    
        public override void TouchID()
        {
            //Some Implementation Here    
        }
    }

    class Samsung : SmartPhone
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }
    }

    class Program
    {
        static void Main(string[] args) { }
    }
}

Apple类继承了TouchID方法并为其提供了定义。现在让我们编译代码,看看会发生什么。

它抛出一个错误,说Samsung类没有实现TouchID方法。根据抽象类的定义,任何实现它的类都必须为其所有抽象成员提供定义。TouchID方法仅用于Apple类,而Samsung类不想实现它。这显然表明我们的方法是错误的,因为TouchID方法不能放在SmartPhone抽象类中。

另一种方法是定义另一个抽象Features,并将TouchID方法定义在其中。这种方法看起来不错,因为任何继承Features的类都可以实现TouchID方法。

using System;

namespace InterfaceDemo
{
    //Abstract Class SmartPhone    
    abstract class SmartPhone
    {
        public abstract void OS();
        public abstract void AppStore();
    }

    //Abstract Class Features for TouchID method   
    abstract class Features
    {
        public abstract void TouchID();
    }

    //Apple Class inherits both SmartPhone and Features   
    class Apple : SmartPhone, Features
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }

        //Implementation of TouchID method in Apple Class    
        public override void TouchID()
        {
            //Some Implementation Here    
        }
    }

    class Samsung : SmartPhone
    {
        public override void OS()
        {
            //Some Implementation Here    
        }

        public override void AppStore()
        {
            //Some Implementation Here    
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

让我们编译代码,看看会发生什么。

它再次抛出一个错误,说我们不能在派生类中有多个基类。这就是所谓的类多重继承,在 C# 中是不允许的。所以,我们的第二种方法在实现TouchID方法时也失败了。这就是接口有用武之地,并有助于解决 C# 中的“多重继承”问题。我们可以将SmartPhoneFeatures都定义为接口,并让类根据需要实现它们。一个类也可以拥有多个接口。这是在 C# 中实现多重继承的唯一方法。

让我们使用接口重新创建相同的项目。我们可以使用关键字“interface”来创建接口。通常的做法是在接口名称前加上“I”前缀,但这观点有争议,选择权在于您。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone       //Definition of Interface    
    {
        public void OS();
        public void AppStore();
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

我们定义了接口ISmartPhone,其中包含方法签名OSAppStore。如果我们现在编译代码,它会立即抛出一个错误。

它说我们不能在方法签名前面加上public修饰符。实际上,接口方法不允许任何访问修饰符。在 C# 中,接口方法隐式地是public的,因为接口是一个供其他类使用的约定。此外,当我们为接口成员提供实现时,我们必须在派生类中将这些方法声明为public。同样,我们也不能将这些方法声明为static

using System;

namespace InterfaceDemo
{
    interface ISmartPhone //Definition of Interface   
    {
        static void OS();
        static void AppStore();
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

如果我们编译代码,它又会给我们一个错误。

让我们不带任何访问修饰符定义接口方法,并创建一个继承ISmartPhone接口的具体类Apple,并为其成员提供定义。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone
    {
        void OS();
        void AppStore();
    }

    class Apple : ISmartPhone
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this Smartphone is iOS8");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this Smartphone is iTunes");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

这里应该注意的一个重要点是,每当我们为派生类中的接口成员提供实现时,访问修饰符必须始终是public,否则会抛出错误。如果我们使用protected修饰符而不是public来修饰OS方法,编译器会抛出错误。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone
    {
        void OS();
        void AppStore();
    }

    class Apple : ISmartPhone
    {
        //OS Method kept as Protected   
        protected void OS()
        {
            Console.WriteLine("OS Method: The OS of this Smartphone is iOS8");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this Smartphone is iTunes");
        }
    }

    class Program
    {
        static void Main(string[] args) { }
    }
}

在上面的代码中,我将OS方法的访问修饰符从public改为了protected。让我们编译代码,看看会发生什么。

是的,它抛出了一个错误,说Apple类不能实现OS方法,因为它不是public的。所以,在派生类中始终将方法实现设为public。我们可以定义另一个具体类Samsung,它也实现ISmartPhone接口并为其成员提供定义。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone
    {
        void OS();
        void AppStore();
    }

    class Apple : ISmartPhone
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this Smartphone is iOS8");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this smartphone is iTunes");
        }
    }

    class Samsung : ISmartPhone
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this smartphone is Android");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this smartphone is Google Play");
        }
    }

    class Program
    {
        static void Main(string[] args) { }
    }
}

这段代码运行良好,因为各种具体类实现了接口,并以自己的方式为成员提供了定义。现在,如果Apple类想实现TouchID功能,只需定义另一个接口IFeatures即可轻松实现。Apple类可以简单地继承该接口,并将其TouchID功能添加到其类中。这就是接口比抽象类有用的情况。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone
    {
        void OS();
        void AppStore();
    }

    //New Interface meant only for Apple Class    
    interface IFeatures
    {
        void TouchID();
    }

    class Apple : ISmartPhone, IFeatures
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this smartphone is iOS8");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this smartphone is iTunes");
        }

        //TouchID Method Implementation    
        public void TouchID()
        {
            Console.WriteLine("TouchID Method: This method provides Touch/Gesture control features.");
        }
    }

    class Samsung : ISmartPhone
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this smartphone is Android");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine
              ("AppStore Method: The Application Store of this smartphone is Google Play");
        }
    }

    class Program
    {
        static void Main(string[] args) { }
    }
}

这样,我们就可以在 C# 中实现多重继承了。让我们创建AppleSamsung具体类的对象并构建项目。

using System;

namespace InterfaceDemo
{
    interface ISmartPhone
    {
        void OS();
        void AppStore();
    }

    //New Interface meant only for Apple Class    
    interface IFeatures
    {
        void TouchID();
    }

    class Apple : ISmartPhone, IFeatures
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this smartphone is iOS8");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine("AppStore Method: The Application Store of this smartphone is iTunes");
        }

        //TouchID Method Implementation    
        public void TouchID()
        {
            Console.WriteLine("TouchID Method: This method provides Touch/Gesture Control features.");
        }
    }

    class Samsung : ISmartPhone
    {
        //OS Method Implementation    
        public void OS()
        {
            Console.WriteLine("OS Method: The OS of this smartphone is Android");
        }

        //AppStore Method Implementation    
        public void AppStore()
        {
            Console.WriteLine
               ("AppStore Method: The Application Store of this smartphone is Google Play");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("//////////////////// - Interface Demo - //////////////////// \n");
            Console.WriteLine("Apple SmartPhone:");
            Apple apple = new Apple();
            apple.OS();
            apple.AppStore();
            apple.TouchID();

            Console.WriteLine("\n\n");
            Console.WriteLine("Samsung SmartPhone:");
            Samsung samsung = new Samsung();
            samsung.OS();
            samsung.AppStore();
            Console.ReadKey();
        }
    }
}

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

这是使用接口的最简单示例。然而,这只是一个现实世界的类比,并且这种方法可能存在争议。我在这里演示的目的是让初学者了解如何使用接口。以下是使用接口时应牢记的关键点:

要点

  • 接口引用变量:接口没有实现,也不能被实例化。但是,它可以引用实现它的类对象。需要注意的是,该对象只能访问接口的继承成员。考虑以下代码
    using System;
    
    namespace InterfaceDemo
    {
        interface IDriveable
        {
            void Drive();
        }
    
        class Car : IDriveable
        {
            public void Drive()
            {
                Console.WriteLine("Car Class: I can drive a Car.");
            }
        }
    
        class Truck : IDriveable
        {
            public void Drive()
            {
                Console.WriteLine("Truck Class: I can drive a Truck.");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("//////////////////// - Interface Demo - //////////////////// \n");
                IDriveable DriveCar = new Car();
                IDriveable DriveTruck = new Truck();
    
                DriveCar.Drive();         //Calls Car's Drive() method   
                DriveTruck.Drive();       //Calls Truck's Drive() method   
                Console.ReadKey();
            }
        }
    }

    代码显示了具有相同接口引用但具有不同功能的对象的声明。

  • 显式接口实现:在使用接口时,可能会出现这种情况:一个类实现了两个接口,并且这两个接口都包含一个具有相同签名的成员。当类为接口成员提供定义时,它会混淆哪个成员获得定义,因为两者名称相同。在这种情况下,我们将使用显式接口实现。假设我们有两个接口ICredtCardIDebitCard,并且这两个接口都具有相同的CardNumber方法签名,并且Customer类实现了这两个接口。
    using System;
    
    namespace InterfaceDemo
    {
        //First Interface IDebitCard   
        interface IDebitCard
        {
            void CardNumber();
        }
    
        //Second Interface ICreditCard    
        interface ICreditCard
        {
            void CardNumber();
        }
    
        //Customer Class implementing both the Interfaces    
        class Customer : IDebitCard, ICreditCard
        {
        }
    
        class Program
        {
            static void Main(string[] args) { }
        }
    }

    有两种方法可以为派生类中的接口成员提供方法定义。如果您右键单击接口名称,Visual Studio 会提供两个选项来实现它们。

    如果我们正常实现接口并为CardNumber方法提供定义,它将导致两个接口都使用CardNumber作为它们的实现。我们不能为接口成员提供不同的功能。

    using System;
    
    namespace InterfaceDemo
    {
        //First Interface IDebitCard    
        interface IDebitCard
        {
            void CardNumber();
        }
    
        //Second Interface ICreditCard    
        interface ICreditCard
        {
            void CardNumber();
        }
    
        //Customer Class implements both the interfaces    
        class Customer : IDebitCard, ICreditCard
        {
            public void CardNumber()
            {
                Console.WriteLine("Card Number: My Card Number is 12345678901234567890");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            { }
        }
    }

    如果我们现在编译程序,输出会引起更多混淆,因为我们无法确定实现了哪个接口方法,因为两个接口都共享CardNumber作为它们的方法。在这种情况下,我们需要通过在派生类中使用显式实现来告诉编译器哪个方法特定于哪个接口。这可以通过在派生类的方法定义前加上接口名称来完成。需要注意的是,显式接口定义自动是public的,因此方法定义不允许使用访问修饰符。我们仍然可以在其中包含共享的方法定义。

    using System;
    
    namespace InterfaceDemo
    {
        //First Interface IDebitCard    
        interface IDebitCard
        {
            void CardNumber();
        }
    
        //Second Interface ICreditCard    
        interface ICreditCard
        {
            void CardNumber();
        }
    
        //Customer Class implements both the interfaces    
        class Customer : IDebitCard, ICreditCard
        {
    
            void IDebitCard.CardNumber()
            {
                Console.WriteLine("Debit Card Number: My Card Number is 12345XXXXX");
            }
    
            void ICreditCard.CardNumber()
            {
                Console.WriteLine("Credit Card Number: My Card Number is 98999XXXXX");
            }
    
            public void CardNumber()
            {
                Console.WriteLine("Customer ID Number: My ID Number is 54545XXXXX");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("////////////////////- 
                       Implicit and Expliction Implementation -//////////////////// \n\n");
                Customer customer = new Customer();
                IDebitCard DebitCard = new Customer();
                ICreditCard CreditCard = new Customer();
    
                customer.CardNumber();
                DebitCard.CardNumber();
                CreditCard.CardNumber();
    
                Console.ReadKey();
            }
        }
    }

    如果我们现在运行程序,我们可以通过显式实现来区分成员。

  • 如果您有一些默认功能要在继承层次结构中的类之间共享,您可以使用抽象类。但是,如果您没有任何要共享的默认实现,而只是需要为派生类定义约定;接口是最优选的选择。
  • 这是一个使用接口的标准规则,请确保您第一次就做对了。一旦接口被派生类实现,就很难更新或修改接口,因为其他人的代码都会出错。

结论

我希望本文能帮助您理解接口的各种可能性。您的反馈和建设性批评一直受到赞赏,请继续提出。在此之前,请努力在宇宙中留下印记。

© . All rights reserved.