C#接口 – 有什么意义?

接口的原因真的没有了我。 从我所了解的情况来看,这对C#中不存在的多重inheritance是一种解决方法(或者我被告知)。

我所看到的是,你预先定义了一些成员和职能,然后再次在class上重新定义。 从而使接口变得冗余。 它只是感觉像句法…好吧,对我来说垃圾(请不要冒犯意味着垃圾在无用的东西)。

在下面的例子中,我从堆栈溢出中的不同的C#接口线程中获得了一个名为Pizza的基类,而不是一个接口。

简单的例子(取自不同的堆栈溢出贡献)

public interface IPizza { public void Order(); } public class PepperoniPizza : IPizza { public void Order() { //Order Pepperoni pizza } } public class HawaiiPizza : IPizza { public void Order() { //Order HawaiiPizza } } 

关键是界面代表一个合同 。 任何实施类都必须具有一套公共方法。 从技术上讲,界面只pipe理语法,即有哪些方法,他们得到什么参数以及返回什么。 通常它们也封装了语义,尽pipe只有文档。

然后你可以有一个接口的不同的实现,并将它们交换出来。 在你的例子中,因为每个披萨实例是一个IPizza你可以使用IPizza无论你处理一个未知披萨types的实例。 由于它有一个Order()方法,所以任何其实例inheritance自IPizza实例IPizza可以保证是可订购的。

Python不是静态types的,因此types在运行时保持不变。 所以你可以尝试在任何对象上调用一个Order()方法。 只要对象有这样一个方法,运行时就很快乐,可能只是耸耸肩说:“呃,如果不行。 在C#中不是这样。 编译器负责进行正确的调用,如果只有一些随机的object ,编译器不知道运行时的实例是否有这个方法。 从编译器的angular度来看,它是无效的,因为它无法validation它。 (你可以用reflection或者dynamic关键字来做这样的事情,但是现在看来我们已经有点儿了。

还要注意,通常意义上的接口并不一定是C# interface ,它可以是抽象类,也可以是普通类(如果所有子类都需要共享一些通用代码,则可以派上用场 – 大多数但是, interface就足够了)。

没有人真正地解释过接口是如何有用的,所以我要给它一个镜头(从Shamim的答案中偷了一下)。

让我们想一下比萨饼订购服务。 你可以有多种types的比萨饼,每个比萨饼正在准备系统中的订单。 每个披萨必须准备,但每个比萨准备不同 。 例如,当订购一个毛绒披萨披萨时,系统可能需要validation餐厅的某些配料,并将那些不需要的深盘披萨放在一边。

当用代码写这个,技术上你可以做

 public class Pizza() { public void Prepare(PizzaType tp) { switch (tp) { case PizzaType.StuffedCrust: // prepare stuffed crust ingredients in system break; case PizzaType.DeepDish: // prepare deep dish ingredients in system break; //.... etc. } } } 

然而,深盘披萨(C#术语)可能需要在Prepare()方法中设置不同的属性,而不是填充的地壳,因此最终会得到很多可选属性,并且该类不能很好地扩展你添加新的比萨types)。

解决这个问题的正确方法是使用接口。 界面声明,所有比萨饼都可以准备,但每个比萨饼可以有不同的准备。 所以如果你有以下接口:

 public interface IPizza { void Prepare(); } public class StuffedCrustPizza : IPizza { public void Prepare() { // Set settings in system for stuffed crust preparations } } public class DeepDishPizza : IPizza { public void Prepare() { // Set settings in system for deep dish preparations } } 

现在您的订单处理代码不需要确切地知道什么types的比萨是为了处理原料而订购的。 它只是有:

 public PreparePizzas(IList<IPizza> pizzas) { foreach (IPizza pizza in pizzas) pizza.Prepare(); } 

即使每种types的披萨都有不同的准备,但这部分代码并不需要关心我们正在处理什么types的披萨,它只是知道它被要求披萨,因此每次调用Prepare将自动准备每个披萨正确的基于它的types,即使集合有多种types的比萨饼。

对于我来说,只有当你停止将它们看作是让你的代码更容易/更快速写入的时候,这一点才变得清晰 – 这不是他们的目的。 他们有很多用途:

(这将会失去比萨饼的比喻,因为想象这种用法并不是很容易)

假设你在屏幕上制作一个简单的游戏,它将会与你交互的生物。

答:通过在您的前端和后端实现之间引入一个松散的耦合,他们可以使您的代码在未来更容易维护。 你可以写这个开始,因为只会有巨魔:

 // This is our back-end implementation of a troll class Troll { void Walk(int distance) { //Implementation here } } 

前端:

 function SpawnCreature() { Troll aTroll = new Troll(); aTroll.Walk(1); } 

两周后,市场营销部门决定你也需要Orcs,因为他们在Twitter上阅读了他们,所以你必须做一些事情:

 class Orc { void Walk(int distance) { //Implementation (orcs are faster than trolls) } } 

前端:

 void SpawnCreature(creatureType) { switch(creatureType) { case Orc: Orc anOrc = new Orc(); anORc.Walk(); case Troll: Troll aTroll = new Troll(); aTroll.Walk(); } } 

你可以看到这开始变得混乱。 你可以在这里使用一个接口,这样你的前端将被写入一次(这里是重要的一点),然后你可以根据需要插入更多的后端项目:

 interface ICreature { void Walk(int distance) } public class Troll : ICreature public class Orc : ICreature //etc 

那么前端是:

 void SpawnCreature(creatureType) { ICreature creature; switch(creatureType) { case Orc: creature = new Orc(); case Troll: creature = new Troll(); } creature.Walk(); } 

前端现在只关心接口ICreature – 它不会为一个巨魔或一个兽人的内部实现而烦恼,而只关心他们实现ICreature的事实。

从这个angular度来看这一点很重要的一点是,你也可以很容易地使用一个抽象的生物类,从这个angular度来看,这也有同样的效果。

而且你可以将创作提取到工厂:

 public class CreatureFactory { public ICreature GetCreature(creatureType) { ICreature creature; switch(creatureType) { case Orc: creature = new Orc(); case Troll: creature = new Troll(); } return creature; } } 

而我们的前端将会变成:

 CreatureFactory _factory; void SpawnCreature(creatureType) { ICreature creature = _factory.GetCreature(creatureType); creature.Walk(); } 

前端现在甚至不需要引用Troll和Orc的库(提供工厂在一个单独的库中) – 它不需要任何关于它们的知识。

B:假如你有一些function,只有一些生物会有其他的同质数据结构 ,比如说

 interface ICanTurnToStone { void TurnToStone(); } public class Troll: ICreature, ICanTurnToStone 

前端可以是:

 void SpawnCreatureInSunlight(creatureType) { ICreature creature; switch(creatureType) { case Orc: creature = new Orc(); case Troll: creature = new Troll(); } creature.Walk(); if (creature is ICanTurnToStone) { (ICanTurnToStone)creature.TurnToStone(); } } 

C:用于dependency injection

当前端代码和后端实现之间存在非常松散的耦合时,大多数dependency injection框架更容易处理。 如果我们拿上面的工厂例子来让我们的工厂实现一个接口:

 public interface ICreatureFactory { ICreature GetCreature(string creatureType); } 

然后我们的前端可以通过构造函数(通常)注入(例如MVC API控制器):

 public class CreatureController : Controller { private readonly ICreatureFactory _factory; public CreatureController(ICreatureFactory factory) { _factory = factory; } public HttpResponseMessage TurnToStone(string creatureType) { ICreature creature = _factory.GetCreature(creatureType); creature.TurnToStone(); return Request.CreateResponse(HttpStatusCode.OK); } } 

使用我们的DI框架(例如Ninject或Autofac),我们可以设置它们,以便在运行时,只要在构造函数中需要ICreatureFactory,就会创build一个CreatureFactory的实例 – 这使得我们的代码更加简单。

这也意味着当我们为我们的控制器编写一个unit testing时,我们可以提供一个模拟的ICreatureFactory(例如,如果具体的实现需要数据库访问,我们不希望我们的unit testing依赖于此),并轻松地testing我们的控制器中的代码。

D:还有其他用途,比如你有两个项目A和B,因为“遗留”原因结构不好,A有一个对B的引用。然后在B中find需要调用A中已有方法的function。不能使用具体的实现,因为你得到一个循环引用。

你可以在B中声明一个接口,然后实现A中的类。 B中的方法可以通过实现接口的类的实例,即使具体对象是A中的一个types也没有问题。

这里是你的例子重新解释:

 public interface IFood // not Pizza { public void Prepare(); } public class Pizza : IFood { public void Prepare() // Not order for explanations sake { //Prepare Pizza } } public class Burger : IFood { public void Prepare() { //Prepare Burger } } 

上面的例子没有多大意义。 你可以使用类来完成上面所有的例子(抽象类,如果你想它只作为一个合同 ):

 public abstract class Food { public abstract void Prepare(); } public class Pizza : Food { public override void Prepare() { /* Prepare pizza */ } } public class Burger : Food { public override void Prepare() { /* Prepare Burger */ } } 

你得到和接口一样的行为。 你可以创build一个List<Food>然后迭代它,知道哪个类位于顶层。

更充分的例子是多重inheritance:

 public abstract class MenuItem { public string Name { get; set; } public abstract void BringToTable(); } // Notice Soda only inherits from MenuItem public class Soda : MenuItem { public override void BringToTable() { /* Bring soda to table */ } } // All food needs to be cooked (real food) so we add this // feature to all food menu items public interface IFood { void Cook(); } public class Pizza : MenuItem, IFood { public override void BringToTable() { /* Bring pizza to table */ } public void Cook() { /* Cook Pizza */ } } public class Burger : MenuItem, IFood { public override void BringToTable() { /* Bring burger to table */ } public void Cook() { /* Cook Burger */ } } 

然后,您可以将它们全部用作MenuItem而不必关心它们如何处理每个方法调用。

 public class Waiter { public void TakeOrder(IEnumerable<MenuItem> order) { // Cook first // (all except soda because soda is not IFood) foreach (var food in order.OfType<IFood>()) food.Cook(); // Bring them all to the table // (everything, including soda, pizza and burger because they're all menu items) foreach (var menuItem in order) menuItem.BringToTable(); } } 

在没有鸭子打字的情况下,你可以在Python中使用它,C#依靠接口来提供抽象。 如果一个类的依赖关系都是具体的types,那么就不能传入任何其他的types – 使用可以通过任何types实现接口的接口。

比萨的例子是不好的,因为你应该使用一个处理sorting的抽象类,比萨应该重写比萨types,例如。

当你有一个共享属性时,你使用接口,但是你的类inheritance了不同的地方,或者当你没有任何你可以使用的通用代码。 比如,这个被使用的东西,可以放置IDisposable ,你知道它会被丢弃,你只是不知道当它被丢弃时会发生什么。

一个接口只是一个契约,告诉你一些对象可以做的事情,参数和期望的返回types。

考虑一下你不控制或拥有基类的情况。

以可视化控件为例,在Winforms的.NET中,它们都是从.NET框架中完全定义的基类Controlinheritance而来的。

假设您正在创build自定义控件。 你想build立新的button,文本框,列表视图,网格,什么都不喜欢,他们都有一些独特的控制function。

例如,你可能想要一个通用的方式来处理主题,或者处理本地化的常见方法。

在这种情况下,你不能“只创build一个基类”,因为如果你这样做,你必须重新实现与控件有关的所有东西

相反,你会从Button,TextBox,ListView,GridView等下降,并添加你的代码。

但是这会产生一个问题,你现在怎么能够确定哪些控件是你的,你怎样才能build立一些代码,说明“我的表单上的所有控件,把主题设置为X”。

input接口。

接口是一种查看对象的方法,以确定对象是否遵守某个契约。

您将创build“YourButton”,从Button下载,并添加对所有您需要的接口的支持。

这将允许您编写如下代码:

 foreach (Control ctrl in Controls) { if (ctrl is IMyThemableControl) ((IMyThemableControl)ctrl).SetTheme(newTheme); } 

如果没有接口,这将是不可能的,相反,你将不得不编写这样的代码:

 foreach (Control ctrl in Controls) { if (ctrl is MyThemableButton) ((MyThemableButton)ctrl).SetTheme(newTheme); else if (ctrl is MyThemableTextBox) ((MyThemableTextBox)ctrl).SetTheme(newTheme); else if (ctrl is MyThemableGridView) ((MyThemableGridView)ctrl).SetTheme(newTheme); else .... } 

在这种情况下,你可能(也许会)只是定义一个比萨基类,并从它们inheritance。 但是,接口允许你做其他事情无法做到的事情有两个原因:

  1. 一个类可以实现多个接口。 它只是定义了类必须具备的function。 实现一系列的接口意味着一个类可以在不同的地方实现多个function。

  2. 一个接口可以定义在比类或调用者更高的范围内。 这意味着您可以分离function,分离项目依赖项,并将function保留在一个项目或类中,并在其他地方执行。

其中一个含义是,你可以改变正在使用的类,只是要求它实现适当的接口。

考虑你不能在C#中使用多重inheritance,然后再看看你的问题。

接口实际上是一个实现类必须遵循的契约,事实上,它是我所知道的几乎所有devise模式的基础。

在你的例子中,接口被创build,因为那么任何比萨,这意味着实现比萨接口,保证已经实现

 public void Order(); 

你提到的代码后,你可以有这样的事情:

 public void orderMyPizza(IPizza myPizza) { //This will always work, because everyone MUST implement order myPizza.order(); } 

这样你就可以使用多态,而你所关心的只是你的对象对order()的响应。

如果我正在使用API​​绘制形状,则可能需要使用DirectX或graphics调用或OpenGL。 所以,我将创build一个接口,它将从我们所称的实现中抽象出我的实现。

所以你调用一个工厂方法: MyInterface i = MyGraphics.getInstance() 。 然后,你有一个合同,所以你知道你可以在MyInterface预期什么function。 因此,您可以调用i.drawRectanglei.drawCube并知道如果将一个库换成另一个库,则支持这些function。

如果您使用dependency injection,这变得更加重要,因为您可以在XML文件中交换实现。

所以,你可能有一个encryption库可以导出为一般用途,而另一个只对美国公司销售,不同之处在于你改变一个configuration文件,其余的程序不是改变。

这在.NET中用于很多集合,因为您应该只使用Listvariables,不要担心它是ArrayList还是LinkedList。

只要你编码到接口,然后开发人员可以改变实际的实现,程序的其余部分保持不变。

这在unit testing时也是有用的,因为你可以模拟出整个接口,所以,我不必去数据库,而只是返回静态数据的模拟实现,所以我可以testing我的方法,而不用担心如果数据库closures维护或不。

解决的问题

所以我是一个build筑工地的前辈。

Tradies一直在网站上散步。 我不知道谁会穿过这些门。 但我基本上告诉他们该做什么。

  1. 如果是木匠,我会说:搭build木质脚手架。
  2. 如果是水pipe工,我会说:“build立pipe道”
  3. 如果是电工,我会说:“拉出电缆,用光纤replace它们”。

上述方法的问题是,我必须:(i)知道谁在门口走,而根据谁是谁,我必须告诉他们该做什么。 这意味着我必须知道关于特定交易的一切。 我宁愿不知道有关pipe道的一切。 那是水pipe工的工作! 这是他/她付钱做的!

解决scheme

设想一个场景,无论谁走在门外,我都可以说:“工作()”,他们做的是他们擅长的敬业工作:pipe道工处理pipe道,电工处理电线。

这种方法的好处是:(i)我不需要确切地知道谁是从那扇门走过来的 – 我只需要知道他们将是一种传统,他们可以工作,其次,(ii)我不需要知道有关这个特定行业的任何事情。 这个tradie会照顾的。

所以,而不是这个:

 If(electrician) then electrician.Work() // fixes wires. if(plumber) then plumber.Work() // fixes pipes 

我可以做这样的事情:

 ITradesman tradie = new Plumber(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment. tradie.Work(); // and then tradie will do the work of a plumber, not an electrician. 

概要

一个界面允许你让这个人做他们所分配的工作,而你却不知道他们是谁或者他们可以做什么的细节。

希望这可以帮助。

Interface = contract,用于松耦合 (参见GRASP )。

接口用于应用不同类之间的连接。 例如,你有一个汽车和一个树的课程,

 public class Car { ... } public class Tree { ... } 

你想为这两个类添加一个可燃的function。 但是每个class级都有自己的燃烧方式。 所以你只是做;

 public class Car : IBurnable { public void Burn() { ... } } public class Tree : IBurnable { public void Burn() { ... } } 

你会得到接口,当你需要他们:)你可以学习的例子,但你需要啊哈! 效果才能真正得到它们。

现在你知道什么接口了,只是没有它们的代码。 迟早你会遇到一个问题,使用接口将是最自然的事情。

我很惊讶没有太多的post包含一个接口的最重要的原因: devise模式 。 这是使用契约的更大的概念,虽然它是对机器代码的语法修饰(说实话,编译器可能只是忽略它们),抽象和接口对于OOP,人类理解和复杂的系统架构来说是至关重要的。

让我们把比萨比喻成一个完整的3道菜。 我们仍然会为我们所有的食物类别Prepare()核心的Prepare()接口,但是我们也会为课程select(初学者,主要,甜点)提供抽象声明,以及食物types的不同属性(美味/甜味,素食/非-vegetarian,无麸质等)。

基于这些规范,我们可以实现Abstract Factory模式来概念化整个过程,但是使用接口来确保只有基础是具体的。 其他任何东西都可以变得灵活或者鼓励多态,但是保持实现ICourse接口的不同课程之间的封装。

如果我有更多的时间,我想制定一个完整的例子,或者有人可以扩展这个,但总而言之,一个C#接口将是devise这种types的系统的最佳工具。

这是一个矩形形状的对象的接口:

 interface IRectangular { Int32 Width(); Int32 Height(); } 

它所要求的是你实现方法来访问对象的宽度和高度。

现在我们来定义一个方法,它可以在IRectangular任何对象上工作:

 static class Utils { public static Int32 Area(IRectangular rect) { return rect.Width() * rect.Height(); } } 

这将返回任何矩形对象的面积。

我们来实现一个矩形的类SwimmingPool

 class SwimmingPool : IRectangular { int width; int height; public SwimmingPool(int w, int h) { width = w; height = h; } public int Width() { return width; } public int Height() { return height; } } 

而另一个类也是长方形的House

 class House : IRectangular { int width; int height; public House(int w, int h) { width = w; height = h; } public int Width() { return width; } public int Height() { return height; } } 

鉴于此,您可以在房屋或游泳池上致电Area方法:

 var house = new House(2, 3); var pool = new SwimmingPool(3, 4); Console.WriteLine(Utils.Area(house)); Console.WriteLine(Utils.Area(pool)); 

通过这种方式,您的类可以从任意数量的接口“inheritance”行为(静态方法)。

我在这个页面上search了“合成”一词,但没有看到它。 除了上述的答案之外,这个答案是非常多的。

在面向对象的项目中使用接口的一个绝对重要的原因是,它们允许你偏好合成而不是inheritance。 通过实现接口,您可以将您的实现与您正在应用的各种algorithm分离。

Derek Banas的这个极好的“装饰模式”教程(其中 – 有趣的 – 也使用比萨作为例子)是一个值得说明的例子:

https://www.youtube.com/watch?v=j40kRwSm4VE

接口定义特定function的提供者与对应消费者之间的契约。 它将合同(界面)的实施分离出来。 你应该看看面向对象的体系结构和devise。 你可能想从维基百科开始: http : //en.wikipedia.org/wiki/Interface_(computing)

接口的主要目的是在你和任何其他实现这个接口的类之间build立一个契约,使你的代码解耦并允许可扩展性。

Therese是真正伟大的例子。

另外,在switch语句的情况下,您不再需要在每次希望rio以特定方式执行任务时进行维护和切换。

在比萨的例子中,如果想制作比萨饼,那么界面就是你所需要的,从那里每个比萨饼都照顾它自己的逻辑。

这有助于减less耦合和圈复杂度。 你必须执行这个逻辑,但是你不得不在更广的范围内跟踪。

对于每个比萨,你可以跟踪特定的披萨信息。 其他比萨饼有什么不重要,因为只有其他比萨需要知道。

想想接口的最简单方法就是识别inheritance的含义。 如果CC类inheritanceC类,则意味着:

  1. Class CC can use any public or protected members of class C as though they were its own, and thus only needs to implement things which do not exist in the parent class.
  2. A reference to a CC can be passed or assigned to a routine or variable that expects a reference to a C.

Those two function of inheritance are in some sense independent; although inheritance applies both simultaneously, it is also possible to apply the second without the first. This is useful because allowing an object to inherit members from two or more unrelated classes is much more complicated than allowing one type of thing to be substitutable for multiple types.

An interface is somewhat like an abstract base class, but with a key difference: an object which inherits a base class cannot inherit any other class. By contrast, an object may implement an interface without affecting its ability to inherit any desired class or implement any other interfaces.

One nice feature of this (underutilized in the .net framework, IMHO) is that they make it possible to indicate declaratively the things an object can do. Some objects, for example, will want data-source object from which they can retrieve things by index (as is possible with a List), but they won't need to store anything there. Other routines will need a data-depository object where they can store things not by index (as with Collection.Add), but they won't need to read anything back. Some data types will allow access by index, but won't allow writing; others will allow writing, but won't allow access by index. Some, of course, will allow both.

If ReadableByIndex and Appendable were unrelated base classes, it would be impossible to define a type which could be passed both to things expecting a ReadableByIndex and things expecting an Appendable. One could try to mitigate this by having ReadableByIndex or Appendable derive from the other; the derived class would have to make available public members for both purposes, but warn that some public members might not actually work. Some of Microsoft's classes and interfaces do that, but that's rather icky. A cleaner approach is to have interfaces for the different purposes, and then have objects implement interfaces for the things they can actually do. If one had an interface IReadableByIndex and another interface IAppendable, classes which could do one or the other could implement the appropriate interfaces for the things they can do.

Interfaces can also be daisy chained to create yet another interface. This ability to implement multiple Interfaces give the developer the advantage of adding functionality to their classes without having to change current class functionality (SOLID Principles)

O = "Classes should be open for extension but closed for modification"

There are a lot of good answers here but I would like to try from a slightlt different perspective.

You may be familiar with the SOLID principles of object oriented design. 综上所述:

S – Single Responsibility Principle O – Open/Closed Principle L – Liskov Substitution Principle I – Interface Segregation Principle D – Dependency Inversion Principle

Following the SOLID principles helps to produce code that is clean, well factored, cohesive and loosely coupled. Given that:

"Dependency management is the key challenge in software at every scale" (Donald Knuth)

then anything that helps with dependency management is a big win. Interfaces and the Dependency Inversion Principle really help to decouple code from dependencies on concrete classes, so code can be written and reasoned about in terms of behaviours rather than implementations. This helps to break the code into components which can be composed at runtime rather than compile time and also means those components can be quite easily plugged in and out without having to alter the rest of the code.

Interfaces help in particular with the Dependency Inversion Principle, where code can be componentized into a collection of services, with each service being described by an interface. Services can then be "injected" into classes at runtime by passing them in as a constructor parameter. This technique really becomes critical if you start to write unit tests and use test driven development. 尝试一下! You will quickly understand how interfaces help to break apart the code into manageable chunks that can be individually tested in isolation.

Am I correct then to take another look at Interfaces, The graphical interface (winforms / WPF) is like the Interface. It displays only what the end user will interact with. The end user will then not have to know what went into the design of the application, but would know what they can do with it, based on available options on the form. In OOP view then the idea is to create a structured interface that informs other users of your eg DLL libraries what is available to use and that it's like an guarantee/contract that the field, methods and properties will be available to use (inherit in your classes).

I share your sense that Interfaces are not necessary. Here is a quote from Cwalina pg 80 Framework Design Guidelines "I often here people saying that interfaces specify contracts. I believe this a dangerous myth. Interfaces by themselves do not specify much. …" He and co-author Abrams managed 3 releases of .Net for Microsoft. He goes on to say that the 'contract' is "expressed" in an implementation of the class. IMHO watching this for decades, there were many people warning Microsoft that taking the engineering paradigm to the max in OLE/COM might seem good but its usefulness is more directly to hardware. Especially in a big way in the 80s and 90s getting interoperating standards codified. In our TCP/IP Internet world there is little appreciation of the hardware and software gymnastics we would jump through to get solutions 'wired up' between and among mainframes, minicomputers, and microprocessors of which PCs were just a small minority. So coding to interfaces and their protocols made computing work. And interfaces ruled. But what does solving making X.25 work with your application have in common with posting recipes for the holidays? I have been coding C++ and C# for many years and I never created one once.