我真的不明白接口存在的原因。据我所知,这是c#中不存在的多继承的一种工作(至少我是这么被告知的)。
我所看到的是,您预定义了一些成员和函数,然后必须在类中再次重新定义它们。从而使接口成为冗余。它只是感觉像句法……嗯,垃圾对我来说(请没有冒犯的意思。Junk是指无用的东西)。
在下面的例子中,我将创建一个名为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
}
}
考虑一下不控制或不拥有基类的情况。
以可视化控件为例,在。net for Winforms中,它们都继承自。net框架中完全定义的基类Control。
让我们假设您从事创建自定义控件的业务。你想要建立新的按钮,文本框,列表视图,网格,等等,你希望他们都有特定的功能独特的控件集。
例如,你可能想要一种通用的方法来处理主题,或者一种通用的方法来处理本地化。
在这种情况下,你不能“只创建一个基类”,因为如果你这样做,你必须重新实现所有与控件相关的东西。
相反,您将从按钮,TextBox, ListView, GridView等下降,并添加您的代码。
但这就产生了一个问题,你现在如何识别哪些控件是“你的”,你如何构建一些代码来表明“对于窗体上所有属于我的控件,将主题设置为X”。
输入接口。
接口是一种查看对象、确定对象是否遵守某种约定的方法。
您可以创建“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 ....
}
这么多答案!
尽我所能。呵呵。
首先,你可以使用一个具体的基类和派生类。在这种情况下,你将不得不在基类中为Prepare方法做一个空的或无用的实现,同时使这个方法为虚的,然后派生类将为自己重写这个Prepare方法。在这种情况下,在基类中实现Prepare是无用的。
选择使用接口的原因是必须定义契约,而不是实现。
有一个IPizza类型,它提供了一个功能来准备。这是合同。它是如何准备的是实现,这不是你的把关。这是各种Pizza实现的责任。
这里首选接口或抽象类而不是具体基类,因为您必须创建一个抽象,即Prepare方法。不能在具体基类中创建抽象方法。
现在你可能会说,为什么不用抽象类呢?
所以,当你需要实现100%的抽象时,你需要使用接口。但是当你需要一些抽象和具体的实现时,选择抽象类。它的意思。
例子:假设你所有的披萨都有底料,底料的准备过程是相同的。然而,所有披萨的种类和配料都有所不同。在这种情况下,您可以使用一个抽象方法Prepare和一个具体方法PreparePizzaBase创建一个抽象类。
public abstract class Pizza{
// concrete method which is common to all pizzas.
public PizzaBase PreparePizzaBase(){
// code for pizza base preparation.
}
public abstract void Prepare();
}
public class DeluxePizza: Pizza{
public void Prepare(){
var base=PreparePizzaBase();
// prepare deluxe pizza on pizza base.
}
}
关键是接口代表一个契约。任何实现类都必须拥有的一组公共方法。从技术上讲,接口只控制语法,即有什么方法,它们得到什么参数以及它们返回什么。通常它们也封装语义,尽管只是通过文档。
然后,您可以拥有一个接口的不同实现,并随意交换它们。在您的示例中,由于每个披萨实例都是IPizza,因此在处理未知披萨类型实例的任何地方都可以使用IPizza。任何类型继承自IPizza的实例都保证是可排序的,因为它有一个Order()方法。
Python is not statically-typed, therefore types are kept and looked up at runtime. So you can try calling an Order() method on any object. The runtime is happy as long as the object has such a method and probably just shrugs and says »Meh.« if it doesn't. Not so in C#. The compiler is responsible for making the correct calls and if it just has some random object the compiler doesn't know yet whether the instance during runtime will have that method. From the compiler's point of view it's invalid since it cannot verify it. (You can do such things with reflection or the dynamic keyword, but that's going a bit far right now, I guess.)
还要注意,通常意义上的接口不一定是c#接口,它也可以是一个抽象类,甚至是一个普通类(如果所有子类都需要共享一些公共代码,这可以派上用场——然而,在大多数情况下,接口就足够了)。
如果我正在使用一个API来绘制形状,我可能想使用DirectX或图形调用,或OpenGL。因此,我将创建一个接口,它将从您调用的内容中抽象出我的实现。
所以你调用一个工厂方法:MyInterface i = MyGraphics.getInstance()。然后,你有一个契约,所以你知道你可以在MyInterface中期望什么功能。你可以调用i。drawrectangle或i。drawcube并且知道如果你把一个库换成另一个库,函数是被支持的。
如果您正在使用依赖注入,这就变得更加重要,因为您可以在XML文件中交换实现。
所以,你可能有一个加密库可以导出,供一般使用,而另一个加密库只出售给美国公司,区别在于你改变了配置文件,而程序的其余部分不会改变。
这在。net中的集合中被大量使用,就像你应该只使用,例如,列表变量,不要担心它是一个数组列表还是LinkedList。
只要您编写了接口代码,那么开发人员就可以更改实际的实现,而程序的其余部分则保持不变。
这在单元测试时也很有用,因为您可以模拟出整个接口,因此,我不需要访问数据库,而是只返回静态数据的模拟出来的实现,因此我可以测试我的方法,而不用担心数据库是否需要维护。