[英]What is the difference between an interface and a class, and why I should use an interface when I can implement the methods directly in the class?
我知道这是一个非常基本的问题,但是面试官问了我很狡猾的方式,我很无助:(
我只知道接口的材料或理论定义,并且还在我参与的许多项目中实现了它。 但我真的不明白这为什么以及如何有用。
我也不明白界面中的一件事。 即例如,我们使用
conn.Dispose();
在 finally 块中。 但我没有看到该类正在实现或继承IDisposable
接口( SqlConnection
)类,我的意思是。 我想知道如何只调用方法名称。 同样在同一件事上,我不理解 Dispose 方法是如何工作的,因为我们需要使用我们自己的所有接口方法实现来实现函数体。 那么接口是如何被接受或命名为契约的呢? 直到现在,这些问题一直在我的脑海中盘旋,坦率地说,我从未见过任何能以我能理解的方式解释我的问题的好线索。
像往常一样 MSDN 看起来非常可怕,那里没有任何一行是清楚的(伙计们,请原谅那些从事高级开发的人,我强烈认为任何代码或文章都应该到达任何看到它的人的脑海中,因此就像许多其他人所说的,MSDN没有用)。
面试官说:
他有 5 个方法,他很乐意直接在类中实现它,但是如果您必须使用抽象类或接口,您选择哪一个,为什么? 我确实回答了他在各种博客中读到的所有内容,说抽象类和接口的优缺点,但他不相信,他试图理解“为什么是接口”。 “为什么是抽象类”,即使我只能实现一次相同的方法并且不会改变它。
我在网络上看不到任何地方,我可以得到一篇文章,可以清楚地解释接口及其功能。 我是众多程序员中的一员,他们仍然不了解接口(我知道我使用的理论和方法)但不满足于我清楚地理解它。
当您想创建类似的东西时,接口非常好:
using System;
namespace MyInterfaceExample
{
public interface IMyLogInterface
{
//I want to have a specific 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 a parameter any object that implements 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
。 就像他们问我在MyLogClass
使用WriteLog()
方法需要实现什么MyLogClass
。 他们会在界面中找到答案。
我使用接口的原因之一是因为它增加了代码的灵活性。 假设我们得到了一个以类类型 Account 作为参数的方法,例如:
public void DoSomething(Account account) {
// Do awesome stuff here.
}
问题在于,方法参数是针对帐户的实现而固定的。 如果您永远不需要任何其他类型的帐户,这很好。 以这个例子为例,它使用一个帐户接口作为参数。
public void DoSomething(IAccount account) {
// Do awesome stuff here.
}
此解决方案不是针对实现而固定的,这意味着我可以向它传递 SuperSavingsAccount 或 ExclusiveAccount(均实现 IAccount 接口)并为每个实现的帐户获得不同的行为。
一句话——因为多态!
如果您“为接口编程,而不是实现”,那么您可以将共享相同接口(类型)的不同对象作为参数注入到方法中。 这样您的方法代码就不会与另一个类的任何实现耦合,这意味着它始终可以使用相同接口的新创建的对象。 (开闭原则)
我相信在问这个问题时已经流了很多血,许多人试图通过解释正常人无法理解的类似机器人的术语来解决这个问题。
所以首先。 要了解为什么接口和为什么抽象,您需要了解它们的用途。 我个人在申请 Factory Class 时学到了这两个。 你在这个链接上找到了一个很好的教程
现在让我们根据我已经给出的链接进行挖掘。
您有可能会根据用户要求更改的Vehicle类(例如添加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
}
并且两者都有 Contract Ichoice ,只是说 My Class 应该有 Buy 方法
public interface IChoice
{
string Buy();
}
现在,您会看到,该接口仅强制执行Buy()
方法,但让继承的类决定在实现它时要执行的操作。 这是接口的局限性,使用纯接口,您可能最终会重复一些我们可以使用 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"; } }
}
现在使用工厂类,您可以实现相同的目的,但是在使用抽象时,您让基类执行Buy()
方法。
总结:接口契约让继承类做实现,而抽象类契约可以初始化实现(可以被继承类覆盖)
C# 没有鸭子类型 - 仅仅因为您知道某个方法是跨一组具体类实现的,并不意味着您可以在调用该方法时对它们一视同仁。 实现接口允许您将实现它的所有类视为相同类型的事物,这与该接口定义的内容有关。
这是一个简单的例子:
Array
和List
实现了接口IList
。 下面我们有一个string[]
和一个List<string>
并使用IList仅用一种方法操作它们:
string[] myArray = { "zero", "one", "two", "three", "four"};
List<string> myList = new List<string>{ "zero", "one", "two", "three"};
//a methode that manipulates both of our collections with IList
static void CheckForDigit(IList collection, string digit)
{
Console.Write(collection.Contains(digit)); //checks if the collection has a specific digit
Console.Write("----");
Console.WriteLine(collection.ToString()); //writes the type of collection
}
static void Main()
{
CheckForDigit(myArray, "one"); //True----System.String[]
CheckForDigit(myList, "one"); //True----System.Collections.Generic.List`1[System.String]
//Another test:
CheckForDigit(myArray, "four"); //True----System.String[]
CheckForDigit(myList, "four"); //false----System.Collections.Generic.List`1[System.String]
}
使用界面,您可以执行以下操作:
1)创建分离的接口,提供不同的实现方式,允许一个更有凝聚力的接口。
2)允许接口之间有多个同名方法,因为嘿,你没有冲突的实现,只有一个签名。
3) 你可以独立于你的实现来版本化和分离你的接口,确保满足合同。
4) 你的代码可以依赖抽象而不是具体,允许智能依赖注入,包括注入测试 Mocks 等。
我敢肯定还有更多的原因,这些只是其中的几个。
抽象类允许你有一个部分具体的基础来工作,这与接口不同,但有它自己的特性,比如使用模板方法模式创建部分实现的能力。
接口是对现实(对象)的抽象(类)进行抽象(原型)。
接口是在不提供类提供的实现的情况下指定合同条款。
接口是规格:
接口是设计时工件,用于指定概念的固定行为,因为它是单独的和静态的。
类是实现时间工件,用于指定现实交互和移动时的移动结构。
什么是接口?
当你观察一只猫时,你可以说它是一种有四只爪子、一个头、一个躯干、一条尾巴和一根毛的动物。 你可以看到他会走路、跑步、吃饭和喵喵叫。 等等。
您刚刚定义了一个带有属性和操作的接口。 因此,您没有定义任何作案手法,而只是定义了特性和能力,而不知道事情是如何运作的:您已经定义了能力和区别。
因此,尽管在 UML 中我们称其为类图中的类,但它实际上还不是一个类,因为我们可以定义私有成员和受保护成员以开始深入了解工件。 不要在这里混淆,因为在 UML 中,接口与 C# 中的接口略有不同:它就像是抽象原子的部分访问点。 因此我们说一个类可以实现多个接口。 因此,它是同一件事,但不是,因为 C# 中的接口既用于抽象抽象,又用于将此抽象限制为访问点。 这是两种不同的用途。 因此,UML 中的类表示与编程类的完全耦合接口,而 UML 接口表示编程类的一部分的解耦接口。 实际上,UML 中的类图并不关心实现,它的所有工件都在编程接口级别。 当我们将 UML 类映射到编程类时,它是抽象抽象到具体抽象的转换。 有一种微妙之处可以解释设计领域和编程领域之间的二分法。 因此,从编程接口的角度来看,UML 中的类是一个编程类,同时考虑了内部隐藏的事物。
接口还允许在以笨拙的方式不可用时模拟多重继承。 例如,cat 类将实现从动物接口派生的 cat 接口。 这个 cat 类还将实现这些接口:walk、run、eat 和 make a sound。 这弥补了类级别多重继承的缺失,但是每次您需要重新实现所有内容时,您最多不能像现实本身那样考虑现实。
要理解我们可以参考 Pascal 对象编码,您在一个单元中定义接口和实现部分。 在接口中定义类型,在实现中实现类型:
unit UnitName;
interface
type
TheClass = class
public
procedure TheMethod;
end;
implementation
class procedure TheClass.TheMethod;
begin
end;
在这里,接口部分与 UML 类设计相匹配,而接口类型因此是其他东西。
因此,在我们的业务中,我们有一个词interface来指定两个不同但相似的事物,这是混淆的根源。
同样在 C# 中,编程接口允许在没有真正实现目标的情况下补偿开放类型上真正的泛型多态性的缺失,因为你失去了强类型的 hability。
毕竟,接口对于允许不兼容的系统进行通信而不必担心内存中对象的实现和管理是必要的,就像(分布式)公共对象模型引入的那样。
什么是班级?
在从外部的角度定义了现实的简化之后,您可以从内部的角度对其进行描述:这是您定义数据处理和消息管理以允许您封装的现实变得生动和交互的类,谢谢到使用实例的对象。
因此,在 UML 中,您实现了机械轮子中的分形沉浸,并描述了状态、交互等,以便能够实现您想要处理的现实片段的抽象。
因此,从编译器的角度来看,抽象类在某种程度上相当于接口。
更多信息
您只能从一个抽象类继承。 您可以从多个接口继承。 这决定了我在大多数情况下使用什么。
抽象类的优点是您可以拥有基本实现。 然而,在 IDisposable 的情况下,默认实现是无用的,因为基类不知道如何正确清理。 因此,接口会更合适。
抽象类和接口都是契约。
契约的想法是你指定一些行为。 如果你说你已经实施了,你就同意了合同。
抽象而不是接口的选择是。
抽象类的任何非抽象后代都将实现契约。
相对
任何实现接口的类都将实现契约。
因此,当您想要指定所有后代必须实现的某些行为并保存自己定义一个单独的接口时,您可以使用抽象,但现在满足此有效聚合契约的所有内容都必须是后代。
当然,在很多情况下,您无需声明或实现任何接口就可以构建一个可工作的软件系统:任何面向对象的软件设计都可以仅使用类来实现。
再说一次,任何软件系统也可以用汇编语言实现,或者用机器代码更好。 我们使用抽象机制的原因是因为它们倾向于使事情变得更容易。 接口就是这样一种抽象机制。
因此,碰巧有某些非平凡的面向对象设计,如果您使用接口,它们将更容易实现,在这些情况下,接口实际上变得必要。
这些非平凡的设计与多重继承有关,在其“真实”形式中,一个类不仅从一个基类继承,而且从两个或多个基类继承。 这种真实的形式在 C# 中是不可能的,但是在 C# 和 Java 等语言出现之前,统治的语言是 C++,它完全支持真正的多重继承。 不幸的是,真正的多重继承被证明不是一个很好的主意,因为它极大地使语言的设计复杂化,并且还会引发各种问题,例如著名的“钻石问题”。 (参见“多重继承的确切问题是什么?”J Francis 的回答)
因此,如果有人想要构建一个“飞行烤面包机”类,他们将继承一些现有的“烤面包机”类以及一些现有的“飞行”类。 他们可能遇到的问题是烤面包机类的电源很可能是墙上的插座,而飞行器类的电源很可能是鸽子食,由此产生的新类要么不知何故两者都有,或者不清楚它会有哪一个。 (钻石问题。)
C# 和 Java 等语言的创建者决定不允许真正的多重继承,以保持语言简单并避免钻石问题等陷阱。 然而,某种形式的多重继承仍然是必要的(或者至少非常需要),因此在这些语言中,他们引入了接口作为支持较少形式的多重继承的一种手段,同时避免了真正多重继承的问题和复杂性。
在这种较少形式的多重继承中,不允许一个类从多个基类继承,但至少可以从一个或多个接口继承。 所以,如果你想构建一个飞行烤面包机,你不能同时继承一些现有的烤面包机类和一些现有的飞行类,但你可以做的是从现有的烤面包机类继承,然后公开一个你自己实现的飞行接口,可能使用您已经从烤面包机继承的任何方式。
因此,除非您觉得需要创建一个聚合两个不同且不相关的功能集的类,否则您不需要任何形式的多重继承,因此您不需要声明或实现任何接口。
接口允许类设计者为最终用户提供非常清晰的可用方法。 它们也是多态性的组成部分。
我不会针对抽象类发布接口的定义,因为我认为您非常了解理论,并且我假设您了解 SOLID 原则,所以让我们开始实践。
如您所知,接口不能有任何代码,因此缺点很容易理解。
如果您需要初始化提供构造函数的类的属性,或者您想提供实现的一部分,则抽象类将非常适合不允许您这样做的接口。
因此,一般而言,当您需要向将继承/扩展您的类的客户端提供构造函数或任何代码时,您应该更喜欢抽象类而不是接口
抽象类是为相关实体创建的,而接口可用于不相关的实体。
例如,如果我有两个实体,比如 Animal 和 Human,那么我会选择 Interface,好像我必须详细说 Tiger,lion 并且想要与 Animal 联系,然后会选择 Animal Abstract 类。
看起来像下面
Interface
____|____
| |
Animal Human
Animal (Abstract class)
__|___
| |
Tiger Lion
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.