我知道这是一个非常基本的问题,但是一位采访者以非常狡猾的方式问我,我很无奈:(
我只知道接口的材料或理论定义,并且在我工作的许多项目中也实现了它。但我真的不明白为什么以及如何有用。
我也不了解界面中的一件事。例如,我们使用
终于阻止 conn.Dispose();
。但我没有看到该类正在实现或继承IDisposable
接口(SqlConnection
)类我的意思。我想知道如何调用方法名称。同样,我不了解Dispose方法是如何工作的,因为我们需要使用我们自己的实现为所有接口方法实现函数体。接口如何被接受或命名为合同?到目前为止,这些问题一直在我脑海中浮现,坦率地说,我从来没有看到任何能以我能理解的方式解释我的问题的好线程。
MSDN像往常一样看起来非常可怕,并且没有一行是明确的(伙计们,善意的理由是谁进入高级开发,我强烈认为任何代码或文章应该达到任何看到它的人的想法,因此像许多人说的那样,MSDN没有用)。
面试官说:他有5种方法,他很乐意直接在课堂上实现它,但如果你必须选择抽象类或接口,你选择哪一种,为什么?我确实回答了他在各种博客中读到的所有内容,说明抽象类和界面的优缺点,但他不相信,他试图理解“为什么界面”。 “为什么抽象类”一般来说,即使我只能实施相同的方法一次而不是改变它。
我看不到网络中的任何地方,我可以得到一篇文章,清楚地解释我的接口及其功能。我是众多程序员中的一员,他们仍然不了解接口(我知道我使用过的理论和方法)但不满意我清楚地理解它。
答案 0 :(得分:84)
当您想要创建类似的东西时,界面非常出色:
using System;
namespace MyInterfaceExample
{
public interface IMyLogInterface
{
//I want to have a especific method that I'll use in MyLogClass
void WriteLog();
}
public class MyClass:IMyLogInterface
{
public void WriteLog()
{
Console.Write("MyClass was Logged");
}
}
public class MyOtherClass :IMyLogInterface
{
public void WriteLog()
{
Console.Write("MyOtherClass was Logged");
Console.Write("And I Logged it different, than MyClass");
}
}
public class MyLogClass
{
//I created a WriteLog method where I can pass as parameter any object that implement IMyLogInterface.
public static void WriteLog(IMyLogInterface myLogObject)
{
myLogObject.WriteLog(); //So I can use WriteLog here.
}
}
public class MyMainClass
{
public void DoSomething()
{
MyClass aClass = new MyClass();
MyOtherClass otherClass = new MyOtherClass();
MyLogClass.WriteLog(aClass);//MyClass can log, and have his own implementation
MyLogClass.WriteLog(otherClass); //As MyOtherClass also have his own implementation on how to log.
}
}
}
在我的示例中,我可以是编写MyLogClass
的开发人员,而其他开发人员可以创建他们的类,当他们想要记录时,他们实现了接口IMyLogInterface
。就像他们在WriteLog()
中询问我需要实施什么才能使用MyLogClass
方法一样。他们将在界面中找到答案。
答案 1 :(得分:42)
我使用接口的一个原因是它增加了代码的灵活性。让我们说我们得到了一个方法,它将类类型Account的对象作为参数,例如:
public void DoSomething(Account account) {
// Do awesome stuff here.
}
问题在于,方法参数是针对帐户的实现而修复的。如果您永远不需要任何其他类型的帐户,这很好。以此示例为例,它使用帐户接口作为参数。
public void DoSomething(IAccount account) {
// Do awesome stuff here.
}
此解决方案未针对实现进行修复,这意味着我可以向其传递SuperSavingsAccount或ExclusiveAccount(均实现IAccount接口)并为每个已实现的帐户获取不同的行为。
答案 2 :(得分:38)
接口是实施者必须遵循的合同。 抽象类允许合同和共享实现 - 接口不能拥有的东西。类可以实现和继承多个接口。类只能扩展一个抽象类。
IDbCommand
有SqlCommand
和OracleCommand
以特定方式实现接口)答案 3 :(得分:19)
总之 - 由于多态性!
如果您“编程到接口而不是实现”,那么您可以将共享相同接口(类型)的不同对象作为参数注入方法中。这样,您的方法代码不会与另一个类的任何实现相结合,这意味着它始终可以使用相同接口的新创建的对象。 (开放/关闭原则)
答案 4 :(得分:17)
所以在这个例子中,PowerSocket对其他对象一无所知。这些对象都依赖于PowerSocket提供的Power,所以他们实现了IPowerPlug,这样他们就可以连接到它。
接口非常有用,因为它们提供了对象可以用来协同工作的合同,而无需了解彼此之间的任何其他内容。
答案 5 :(得分:4)
C#没有鸭子输入 - 只是因为你知道在一组具体类中实现某个方法并不意味着你可以在调用该方法时对它们进行相同的处理。实现接口允许您将实现它的所有类视为相同类型的事物,关于接口定义的内容。
答案 6 :(得分:1)
您只能从一个抽象类继承。您可以从多个接口继承。这决定了我在大多数情况下使用的内容。
抽象类的优点是你可以拥有一个基本实现。但是,在IDisposable的情况下,默认实现是无用的,因为基类不知道如何正确地清理。因此,界面会更合适。
答案 7 :(得分:1)
使用界面,您可以执行以下操作:
1)创建隔离的界面,提供不同的实现削减,从而实现更具凝聚力的界面。
2)在接口之间允许多个具有相同名称的方法,因为嘿,你没有冲突的实现,只是一个签名。
3)您可以独立于您的实施进行版本和配置,确保合同得到满足。
4)你的代码可以依赖抽象而不是具体化,允许智能依赖注入,包括注入测试模拟等。
我确信还有很多原因,这些只是少数原因。
抽象类允许您使用部分具体的基础,这与接口不同,但具有自己的特性,例如使用模板方法模式创建部分实现的能力。
答案 8 :(得分:1)
抽象类和接口都是契约。
合同的概念是指定一些行为。如果你说你已经实施了你已经同意合同。
在接口上选择抽象是。
抽象类的任何非抽象后代都将实现契约。
与
任何实现接口的类都将实现合同。
因此,当您想要指定所有后代必须实现的某些行为时,您使用抽象,并保存自己定义单独的接口,但现在满足此有效聚合合同的所有内容必须是后代。
答案 9 :(得分:1)
我相信在提出这些问题时已经有许多血液泄漏了,许多人试图通过解释没有普通人能够理解的类似机器人的术语来解决这个问题。
首先。了解为什么界面和为什么抽象你需要了解它们的用途。在应用Factory Class时,我个人学会了这两个。你找到了一个好的tuturial on this link
现在让我们根据我已经提供的链接进行挖掘。
您有车辆类可能会根据用户要求进行更改(例如添加 Truck , Tank , Airplane ,等等我们有
public class clsBike:IChoice
{
#region IChoice Members
public string Buy()
{
return ("You choose Bike");
}
#endregion
}
和
public class clsCar:IChoice
{
#region IChoice Members
public string Buy()
{
return ("You choose Car");
}
#endregion
}
并且两者都有契约IChoice,简单地说我的班级应该有买方法
public interface IChoice
{
string Buy();
}
现在,您可以看到,该接口仅强制执行方法Buy()
,但让继承的类决定在实现它时要执行的操作。这是Interface的限制,使用纯粹的接口,你最终可能会重复一些我们可以使用abstact自动实现的任务。我们的例子就是说,购买每辆车都有折扣。
public abstract class Choice
{
public abstract string Discount { get; }
public abstract string Type { get; }
public string Buy()
{
return "You buy" + Type + " with " + Discount;
}
public class clsBike: Choice
{
public abstract string Discount { get { return "10% Discount Off"; } }
public abstract string Type { get { return "Bike"; } }
}
public class clsCar:Choice
{
public abstract string Discount { get { return " $15K Less"; } }
public abstract string Type { get { return "Car"; } }
}
现在使用Factory类,您可以实现相同的功能,但在使用abstract时,您可以让基类执行Buy()
方法。
在摘要中:接口合同让继承类执行实现 而抽象类合同可能会初始化实现(可以通过Inherit类覆盖)
答案 10 :(得分:1)
作为@ user2211290答案的真实示例:
Array
和List
都具有接口IList
。下面我们有一个string[]
和一个List<string>
,并使用 IList 进行一些常见测试:
string[] col1 = { "zero", "one", "two", "three", "four"};
List<string> col2 = new List<string>{ "zero", "one", "two", "three"};
static void CheckForDigit(IList collection, string digit)
{
Console.Write(collection.Contains(digit));
Console.Write("----");
Console.WriteLine(collection.ToString());
}
static void Main()
{
CheckForDigit(col1, "one"); //True----System.String[]
CheckForDigit(col2, "one"); //True----System.Collections.Generic.List`1[System.String]
//Another test:
CheckForDigit(col1, "four"); //True----System.String[]
CheckForDigit(col2, "four"); //false----System.Collections.Generic.List`1[System.String]
}
答案 11 :(得分:1)
接口应在不提供类提供的实现的情况下指定合同条款。
接口是规范:
接口是设计时的工件,用于指定概念的固定行为,因为它既单独又是静态的。
类是实现时的工件,用于指定现实在交互和移动时的移动结构。
当您观察猫时,您可以说它是具有四个爪子,头,躯干,尾巴和头发的动物。您可以看到他可以走路,跑步,吃饭和喵喵叫。依此类推。
您刚刚使用其属性和操作定义了一个接口。因此,您没有定义任何形式的操作,而仅定义了功能和特性而又不知道事物如何工作:您已经定义了功能和区别。
因此,即使在UML中我们将其称为类图中的类,它实际上还不是一个类,因为我们可以定义私有和受保护的成员,以开始对工件进行深入了解。这里不要混淆,因为在UML中,接口与C#中的接口略有不同:它就像是对抽象原子的部分访问点。因此,我们说一个类可以实现多个接口。因此,这是同一回事,但并非如此,因为C#中的接口都用于抽象化抽象并将抽象化限制为访问点。这是两种不同的用途。因此,UML中的类表示与编程类的完整耦合接口,而UML接口则表示编程类的某个部分的解耦接口。确实,UML中的类图并不能解决实现问题,并且其所有工件都在编程接口级别。当我们将UML类映射到编程类时,它是抽象抽象到具体抽象的转换。有一个微妙之处可以解释设计领域和编程领域之间的二分法。因此,从编程接口的角度来看,UML中的类是编程类,同时考虑内部隐藏的事物。
如果无法以尴尬的方式使用接口,还可以模拟多重继承。例如,cat类将实现从动物接口派生的cat接口。这个cat类还将实现以下接口:走路,跑步,吃饭和发出声音。这弥补了在类级别上没有多重继承的不足,但是每次您需要重新实现所有功能时,便无法像现实本身那样充其量地考虑现实。
要理解,我们可以参考Pascal Object编码,您可以在其中单位定义接口和实现部分。在界面中定义类型,并在实现中实现类型:
unit UnitName;
interface
type
TheClass = class
public
procedure TheMethod;
end;
implementation
class procedure TheClass.TheMethod;
begin
end;
在这里,接口部分与UML类设计匹配,而接口类型则是另外一回事。
因此,在我们的业务中,我们用一个词 interface 来提名两个截然不同但相似的事物,这是造成混乱的原因。
例如,在C#中,编程接口允许补偿开放类型上不存在真正的通用多态性,而不会真正实现目标,因为您失去了强类型的稳定性。
毕竟,接口是必需的,以允许不兼容的系统进行通信,而不必担心内存对象的实现和管理,就像(分布式)通用对象模型引入的那样。
从外部角度定义现实的简化之后,您可以从内部的角度进行描述:这是定义数据处理和消息管理的类,以使封装的现实得以实现和使用实例的对象进行交互。
因此,在UML中,您实现了分形沉浸在机器的轮子中,并描述了状态,相互作用等,从而能够实现要处理的现实片段的抽象。
这样,从编译器的角度来看,抽象类在某种程度上等效于接口。
答案 12 :(得分:0)
接口允许类设计者为最终用户提供非常清晰的可用方法。它们也是多态性的组成部分。
答案 13 :(得分:0)
我不会针对抽象类发布接口的定义,因为我认为你非常清楚理论,我认为你知道SOLID原则,所以让我们变得实用。
如您所知,接口不能包含任何代码,因此理解起来非常简单。
如果你需要初始化提供构造函数的类的属性,或者你想提供部分实现,那么抽象类将非常适合于不允许你这样做的接口。
因此,当您需要向客户端提供构造函数或任何将继承/扩展您的类的代码时,您应该更喜欢抽象类到接口
答案 14 :(得分:0)
当然,在许多情况下,您可以构建运行的软件系统而无需声明或实现任何接口:除类之外,任何面向对象的软件设计都可以实现。
然后,任何软件系统也都可以用汇编语言或更好的机器代码实现。我们使用抽象机制的原因是因为它们倾向于使事情变得更容易。接口就是这种抽象机制。
因此,恰好发生了某些非平凡的面向对象设计,如果您使用接口,那么实现起来要容易得多,在这种情况下,实际上需要使用接口。
这些非平凡的设计与多重继承有关,多重继承以其“真实”形式出现,即当一个类不仅从一个基类继承而是从两个或多个基类继承时。这种真正的形式在C#中是不可能的,但是在像C#和Java之类的语言出现之前,统治者是C ++,它完全支持真正的多重继承。不幸的是,事实证明真正的多重继承不是一个好主意,因为它极大地增加了语言的设计复杂度,并且还引发了各种问题,例如著名的“钻石问题”。 (请参见"What is the exact problem with multiple inheritance?" answer by J Francis)
因此,如果有人想建立一个“ flying toaster”类,那么他们将从某些现有的“ toaster”类以及某些现有的“ flying”类中继承。他们可能会遇到的问题是,烤面包机类的电源可能是壁装插座,而飞行器类的电源可能是鸽子粮,而由此产生的新类将是两者兼而有之,或者不清楚会拥有哪一个。 (钻石问题。)
C#和Java之类的语言的创建者决定不允许真正的多重继承,以保持该语言的简单性并避免诸如Diamond问题之类的陷阱。但是,仍然需要某种形式的多重继承(或至少非常非常需要),因此在这些语言中,他们引入了接口,以支持较少形式的多重继承,同时避免了真正多重继承的问题和复杂性。
在这种较少的多重继承形式中,不允许有一个类从多个基类继承,但是至少可以从一个或多个接口继承。因此,如果您要构建飞行烤面包机,则不能同时继承一些现有的烤面包机类和某些飞行类,但是您可以做的是从现有的烤面包机类继承,然后公开一个您自己实现的飞行接口,可能会使用您已经从烤面包机继承的任何方式。
因此,除非您曾经需要创建一个集合两个不同且不相关的功能集的类,否则您将不需要任何形式的多重继承,因此您将不需要声明或实现,任何接口。
答案 15 :(得分:-1)
抽象类是为相关实体创建的,其中接口可用于不相关的实体。
例如,如果我有两个实体说动物和人类,那么我将去接口,好像我必须详细说老虎,狮子,并想与动物然后将选择动物抽象类...
将如下所示
Interface
____|____
| |
Animal Human
Animal (Abstract class)
__|___
| |
Tiger Lion