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

C# 中的显式接口与隐式接口?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.19/5 (37投票s)

2015 年 6 月 12 日

CPOL

6分钟阅读

viewsIcon

117363

在本文中,我们将讨论显式接口,它们与隐式接口有何不同,以及为什么应该避免使用它们。

目录

引言

什么是显式和隐式接口?

我们什么时候需要显式接口?

更清晰的方法:- ISP(接口隔离原则)

子类无法访问显式实现的接口。

使代码更难阅读

隐藏不推荐的接口成员

关于显式接口的误解

关于显式接口的结论

引言

在本文中,我们将讨论显式接口,它们与隐式接口有何不同,以及为什么应该避免使用它们。

什么是显式和隐式接口?

实现 C# 接口有两种方式:“显式”和“隐式”。

当你隐式实现一个接口时,代码如下所示。“IDal”是接口,“Add”和“Update”是隐式实现的方法。大多数开发者都使用隐式方式实现接口。

public class Person : IDal
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public void Add()
        {
            throw new NotImplementedException();
        }

        public void Update()
        {
            throw new NotImplementedException();
        }
}

如果你显式实现一个接口,代码如下所示。区别在于“Add”和“Update”方法现在是私有的

public class Person : IDal
{
        public string FirstName { get; set; }
        public string LastName { get; set; }


void IDal.Add()
        {
            throw new NotImplementedException();
        }

        void IDal.Update()
        {
            throw new NotImplementedException();
        }
}

简单来说,“隐式”接口和“显式”接口的区别在于,隐式实现中接口方法是公开的,而显式实现中方法是私有的。

那么接下来的问题是,为什么会有这种公开与私有的区别,以及我们应该在什么时候使用“隐式”接口,什么时候使用“显式”接口。

我们什么时候需要显式接口?

让我在这里声明一点:-

当你的具体类的抽象不包含接口的抽象时,应该使用“显式”接口。这是一种实现双重抽象的快速而粗糙的方法。

我能感觉到理解上面这句话有多难。

让我们放慢节奏,循序渐进,首先来理解“抽象”这个词。

“抽象”意味着从客户端的角度只显示你的类必要的方法/属性。

如果你对“抽象”这个词不熟悉,我鼓励你观看这个C# 抽象概念的油管视频

下面是一个简单的“Person”类,包含一些属性和方法。

public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public void Add()
        {

        }

        public void Update()
        {

        }
    }

现在这个“Person”类(也就是一个库)被两个客户端使用:

“PersonalInformation”和“DataAccessBatchProcess”。

“PersonalInformation”是一个简单的 UI 应用程序,用于处理“Person”信息;“DataBatchProcess”是一个简单的批处理程序,将这些数据添加到数据库中。

在这种情况下,“Person”是库,“DataAccessBatchProcess”和“PersonalInformation”应用程序是这个库的消费者。

让我来谈谈最佳实践。

一个好的软件架构的标志之一是,你会看到它非常尊重根据客户端配置文件进行的“抽象”。

在这个特定场景中,“Personal Information”应用程序只想看到“FirstName”和“LastName”,而数据访问批处理程序只想看到“Add”和“Update”方法。换句话说,同一个类我们有两种不同的抽象。

一个好的架构能以优雅的方式应对变化。在好的架构中,当任何变更请求被执行时,代码的改动应该最少。

只有当抽象被正确实现时,才能做到最少的代码改动。

因为通过抽象,每个消费者只能看到必要的东西,系统中不必要/不相关的变更不会影响到它们。

所以第一步是创建一个只包含“Add”和“Update”方法的 Dal 接口。

public interface IDal
{
        void Add();
        void Update();
}

然后我们隐式地实现“IDal”接口。

public class Person : IDal
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public void Add()
        {
        // Here goes the data access layer add code
        }

        public void Update()
        {
// Here goes the data access layer update code
        }
    }

所以现在当我们从“IDal”接口的角度来看“Person”类时,我们只看到“Add”和“Update”方法。对于数据访问层的批处理程序来说,后台是什么业务对象并不重要。

它可以是任何类型的业务对象——person、customer、account——对它来说重要的是“Update”和“Add”方法。

但现在让我们测试一下“Personal Information”应用程序的抽象是否得到了尊重。

你可以看到,当我们创建“Person”类的对象时,我们也能看到“Update”和“Add”方法。换句话说,“Personal Information”应用程序看到了不必要的东西。

所以如果将来对“Update”和“Add”方法进行任何更改,它们不应该不必要地影响到“Personal Information”应用程序。

一个糟糕架构的标志是,当一个变更请求被执行时,不相关的部分也发生了变化。

因此,解决这个问题的快速而粗糙的方法是使用“显式”接口。请注意,“快速而粗糙的方法”这个词被加粗了,我们很快会讨论它。

所以如果我们“显式地”实现“IDal”接口,代码会是下面这个样子。

最重要的一点是,“Add”和“Update”方法变成了私有的。

public class Person : IDal
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        void IDal.Add()
        {
            // Add code goes here
        }

        void IDal.Update()
        {
            // Update code goes here.
        }
    }

现在如果我们创建“Person”对象,我们就看不到“Update”和“Add”方法了,这样我们就尊重了“Personal Information”应用程序的抽象。

用数学方式总结一下JJ

类的公共成员 = (类的公共成员 + 隐式接口成员) - 显式接口成员

更清晰的方法:- ISP(接口隔离原则)

上述方法是为类实现多种抽象的一种快速而粗糙的方式。应尽可能避免使用显式接口。

正确的方式是遵循 SOLID 原则并实现 ISP(接口隔离原则)。如果你对 SOLID 原则不熟悉,请阅读这篇文章使用 C# 理解 SOLID 原则

所以,为 Dal 和 Person 创建两个不同的接口,这样各自的抽象会更加清晰。

子类无法访问显式实现的接口。

如果你显式地实现一个接口,其成员会变成私有的,你的子类就无法重写这些成员,从而破坏了整个继承体系。

在下面的例子中,我的“Person”类被“Employee”继承。但因为接口是显式实现的,“Add()”和“Update()”变成了私有方法,因此我失去了链式多态的好处。

public class Person : IDal
{

        void IDal.Add()
        {
            throw new NotImplementedException();
        }
        void IDal.Update()
        {
            throw new NotImplementedException();
        }
}

public class Employee : Person
{

}

使代码更难阅读

接口的重点在于它是一种契约。是两个类之间的契约。多年来,开发者们都知道契约通常是公开的。但在这种情况下,接口方法突然变成了私有的,这也会在可读性方面引起混淆。

隐藏不推荐的接口成员

我唯一觉得需要使用显式接口的时候是,当我想让所实现接口的某些方法是私有的,而另一些方法是公开的。

例如,在下面的类中,我正在实现“IEnumerable”接口,我希望客户端能够访问“GetEnumerator”,但不能访问“Enumerable”。

public class Person : IEnumerable<string>
    {


public IEnumerator<string> GetEnumerator()
        {
throw new NotImplementedException();
        }

System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator()
        {
throw new NotImplementedException();
        }
    }
</string></string>

关于显式接口的误解

关于显式接口的一个误解是,当你需要实现来自不同接口的同名方法时,应该使用它。

观看这个视频来理解我说的“来自不同接口的同名方法”是什么意思:https://www.youtube.com/watch?v=6ZxVx93wa7Q

如果你真的遇到了这种情况,那只能是因为设计糟糕。我不认为这是一个使用显式接口的有力理由。

关于显式接口的结论

  • 应该避免使用显式接口,而应遵循 ISP 原则。换句话说,为不同的抽象创建不同的接口。
  • 显式接口不能被派生类使用。
  • 唯一应该使用它的情况是,出于某种原因,你想要实现接口,但又想通过类隐藏一些方法并显示另一些方法。

我理解我对显式接口做出了一个非常粗鲁的结论。我希望听取社区的意见,获取反馈并加以改进。

请在下面的评论区留下您的评论。

进一步阅读,请观看下面的面试准备视频和分步视频系列。

© . All rights reserved.