我了解它们会迫使您实现方法等,但我无法理解的是为什么要使用它们。谁能给我一个很好的例子或解释我为什么要实现这一点。
Answers:
一个特定的示例:接口是指定其他人的代码必须满足的合同的好方法。
如果我正在编写代码库,则可能会编写对具有某些行为的对象有效的代码。最好的解决方案是在接口(没有实现,仅是描述)中指定这些行为,然后在我的库代码中使用对实现该接口的对象的引用。
然后,任何随机的人都可以出现,创建实现该接口的类,实例化该类的对象,并将其传递给我的库代码,并期望它能工作。注意:当然有可能在不考虑接口意图的情况下严格实现接口,因此仅实现接口并不能保证一切正常。愚蠢总能找到办法!:-)
另一个具体示例:两个团队在必须合作的不同组件上工作。如果两个团队在第一天坐下来并就一组接口达成一致,那么他们可以走自己的路,并围绕这些接口实现其组件。团队A可以构建测试工具,以模拟团队B中的组件进行测试,反之亦然。并行开发,错误更少。
关键是接口提供了抽象层,因此您可以编写忽略不必要细节的代码。
大多数教科书中使用的规范示例是排序例程。您可以对任何类别的对象进行排序,只要您能够比较任何两个对象即可。因此,您可以通过实现IComparable
接口使任何类可排序,这迫使您实现用于比较两个实例的方法。编写所有排序例程都是为了处理对IComparable对象的引用,因此,一旦实现IComparable,就可以在类的对象集合上使用这些排序例程中的任何一个。
接口定义合同,这就是关键词。
当需要在程序中定义合同时,可以使用接口,但实际上并不关心满足该合同的类的其余所有属性。
因此,让我们看一个例子。假设您有一个提供对列表进行排序的功能的方法。第一件事..什么是清单?您是否真的在意列表排序所包含的元素?您的答案应该是否定的。例如,在.NET中,您有一个名为IList的接口,该接口定义了列表必须支持的操作,因此您无需关心表面之下的实际细节。
回到示例中,您实际上并不知道列表中对象的类……也不在乎。如果您只能比较对象,则可以对它们进行排序。所以你宣布一份合同:
interface IComparable
{
// Return -1 if this is less than CompareWith
// Return 0 if object are equal
// Return 1 if CompareWith is less than this
int Compare(object CompareWith);
}
该合同规定必须实现接受对象并返回int的方法,以使其具有可比性。现在,您已经定义了合同,并且现在您不再关心对象本身,而是合同,因此您可以执行以下操作:
IComparable comp1 = list.GetItem(i) as IComparable;
if (comp1.Compare(list.GetItem(i+1)) < 0)
swapItem(list,i, i+1)
PS:我知道这些示例有些天真,但它们只是示例...
IComparable
没有规定你回来1
,0
和-1
但你回来>0
,0
和<0
。这样就可以进行优化,例如只需对int进行排序return this.intNum - CompareWith.intNum
。您不必关心它是大于或小于0,而不必关心它是大于还是小于。
理解接口的最简单方法是,它们允许不同的对象公开COMMON功能。这样,程序员就可以编写简单得多,更短的代码,以便对接口进行编程,然后只要对象实现该接口,它将可以正常工作。
示例1: 有许多不同的数据库提供程序,例如MySQL,MSSQL,Oracle等。但是,所有数据库对象都可以做相同的事情,因此您会发现许多数据库对象接口。如果对象实现IDBConnection,则它将公开方法Open()和Close()。因此,如果我希望我的程序与数据库提供程序无关,那么我将对接口进行编程,而不是对特定提供程序进行编程。
IDbConnection connection = GetDatabaseConnectionFromConfig()
connection.Open()
// do stuff
connection.Close()
通过编程到接口(IDbconnection)来查看,现在我可以在配置中交换掉任何数据提供程序,但是我的代码保持完全相同。这种灵活性非常有用并且易于维护。不利的一面是,我只能执行“通用”数据库操作,而不能充分利用每个特定提供程序所提供的优势,因此在进行编程时,您需要权衡取舍,并且必须确定哪种方案对您最有利。
范例2: 如果您发现几乎所有集合都实现了称为IEnumerable的接口。IEnumerable返回一个IEnumerator,该IEnumerator具有MoveNext(),Current和Reset()。这使C#可以轻松地在您的集合中移动。之所以这样做,是因为它公开了IEnumerable接口,它知道对象公开了它需要通过它的方法。这有两件事。1)foreach循环现在将知道如何枚举集合,并且2)现在可以将功能强大的LINQ表达式应用于集合。同样,接口之所以在这里如此有用的原因是,因为所有集合在COMMON中都有某些内容,因此可以移动它们。每个集合可以通过不同的方式移动(链接列表与数组),但是接口的优点在于实现是隐藏的,与接口的使用者无关。MoveNext()为您提供集合中的下一项,它的执行方式无关紧要。很好,是吗?
示例3: 在设计自己的界面时,您只需要问自己一个问题。这些东西有什么共同点?一旦找到对象共享的所有事物,就可以将这些属性/方法抽象到接口中,以便每个对象都可以从该接口继承。然后,您可以使用一个界面针对多个对象进行编程。
当然,我必须给出我最喜欢的C ++多态示例,即动物示例。所有动物都有某些特征。假设他们可以移动,说话,并且都有名字。因为我刚刚确定了我所有动物的共同点,所以我可以将这些特性抽象到IAnimal接口中。然后,我创建一个Bear对象,一个Owl对象和一个Snake对象,这些对象均实现了此接口。您可以将实现同一接口的不同对象存储在一起的原因是,接口代表IS-A复制关系。熊IS-A是动物,猫头鹰IS-A是动物,所以我可以将它们全部收集为动物。
var animals = new IAnimal[] = {new Bear(), new Owl(), new Snake()} // here I can collect different objects in a single collection because they inherit from the same interface
foreach (IAnimal animal in animals)
{
Console.WriteLine(animal.Name)
animal.Speak() // a bear growls, a owl hoots, and a snake hisses
animal.Move() // bear runs, owl flys, snake slithers
}
您可以看到,即使这些动物以不同的方式执行每个动作,我也可以在一个统一的模型中对它们进行编程,这只是Interfaces的众多优点之一。
因此,接口最重要的一点是对象的共同点,以便您可以以相同的方式针对不同的对象进行编程。节省时间,创建更灵活的应用程序,隐藏复杂性/实现,对现实世界的对象/情况进行建模以及许多其他好处。
希望这可以帮助。
汽车上的踏板实现接口。我来自美国,我们在马路右侧行驶。我们的方向盘在汽车的左侧。从左到右的手动变速踏板为离合器->制动器->油门。当我去爱尔兰时,驾驶方向相反。汽车的方向盘在右侧,它们在道路的左侧行驶...但是踏板,踏板啊,它们实现了相同的界面,所有三个踏板的顺序都相同...因此,即使班级不同并且班级所使用的网络也不相同,我仍然对踏板界面感到满意。我的大脑就像其他每辆车一样,能够在这辆车上呼唤我的肌肉。
想一想我们离不开的众多非编程接口。然后回答您自己的问题。
接口在期望充分利用多态性的面向对象系统中绝对必要。
一个典型的例子是IVehicle,它具有Move()方法。您可能拥有实现IVehicle的Car,Bike和Tank类。他们都可以使用Move(),并且您可以编写不关心它正在处理哪种车辆的代码,就可以使用Move()。
void MoveAVehicle(IVehicle vehicle)
{
vehicle.Move();
}
接口是多态性的一种形式。一个例子:
假设您要编写一些日志记录代码。日志记录将去某个地方(可能是文件,或者是运行主代码的设备上的串行端口,或者是套接字,或者像/ dev / null一样被丢弃)。您不知道在哪里:登录代码的用户需要自由确定。实际上,您的日志记录代码无关紧要。它只想要可以写入字节的内容。
因此,您发明了一个称为“可以向其写入字节的内容”的接口。记录代码为此接口提供了一个实例(也许在运行时,也许是在编译时配置的。它仍然是多态的,只是种类不同)。您编写一个或多个实现该接口的类,并且只需更改日志记录代码将使用的哪一个即可轻松更改日志记录的位置。其他人可以通过编写自己的接口实现来更改日志记录的位置,而无需更改代码。这基本上就是多态性的含义-足够了解一个对象以特定方式使用它,同时允许它在不需要了解的所有方面有所变化。界面描述了您需要了解的事情。
C的文件描述符基本上是一个接口,“我可以在其中读写字节”,几乎每种类型的语言都在其标准库中潜藏着这样的接口:流或其他。无类型语言通常具有表示流的非正式类型(可能称为合同)。因此,在实践中,您几乎不必自己真正发明这个特定接口:您可以使用语言所提供的功能。
日志记录和流只是一个示例-只要您可以抽象地描述对象应该做什么,但不想将其绑定到特定的实现/类/任何对象,接口就会发生。
想象下面的基本接口,它定义了基本的CRUD机制:
interface Storable {
function create($data);
function read($id);
function update($data, $id);
function delete($id);
}
从此接口,您可以知道实现它的任何对象都必须具有创建,读取,更新和删除数据的功能。这可以通过数据库连接,CSV文件读取器和XML文件读取器,或任何其他想要使用CRUD操作的机制实现。
因此,您现在可以拥有以下内容:
class Logger {
Storable storage;
function Logger(Storable storage) {
this.storage = storage;
}
function writeLogEntry() {
this.storage.create("I am a log entry");
}
}
如果您传入数据库连接或操纵磁盘上文件的操作,此记录器将不在乎。它只需要知道可以调用create()就可以了。
那么,由此引起的下一个问题是,如果数据库和CSV文件等都可以存储数据,是否不应该从通用的Storable对象继承它们,从而消除对接口的需求?答案是否定的……不是每个数据库连接都可以实现CRUD操作,并且每个文件读取器都适用相同的要求。
接口定义了对象的功能以及您需要如何使用它……不是对象!
如您所述,当您要强迫某人以某种格式创建界面时,它非常有用。
当数据不是某种格式可能意味着在代码中进行危险的假设时,接口是很好的选择。
例如,目前我正在编写一个将数据从一种格式转换为另一种格式的应用程序。我想强迫他们将这些字段放入其中,以便我知道它们将存在并且有更大的机会被正确实施。我不在乎是否会出现另一个版本,也不会为它们编译,因为无论如何仍然需要数据。
接口很少使用,因为这一点,因为通常你可以假设或并不真正需要的数据,你需要做什么。
首先,它们为您提供了额外的抽象层。您可以说“对于此功能,此参数必须是具有这些参数的方法的对象”。您可能还希望以某种抽象的方式设置这些方法的含义,但允许您对代码进行推理。使用鸭式语言,您可以免费获得。不需要显式的语法“接口”。但是,您可能仍会创建一组概念性界面,例如合同(例如按合同设计)。
此外,有时将接口用于不太“纯粹”的目的。在Java中,它们可用于模拟多重继承。在C ++中,您可以使用它们来减少编译时间。
通常,它们减少了代码中的耦合。这是好事。
您的代码也可能更容易通过这种方式进行测试。
在我博客的一篇文章中,我简要描述了接口的三个用途。
接口可能具有不同的用途:
- 为同一目标提供不同的实现。典型的示例是列表,针对不同的性能用例(LinkedList,ArrayList等)可能具有不同的实现。
- 允许条件修改。例如,基于同一算法,排序功能可以接受Comparable接口,以提供任何种类的排序标准。
- 隐藏实施细节。这也使用户更容易阅读注释,因为在界面的主体中只有方法,字段和注释,没有很长的代码块可以跳过。
这是本文的全文:http : //weblogs.manas.com.ar/ary/2007/11/
在C#接口中,对于允许不共享相同基类的类多态性也非常有用。意思是,由于我们不能具有多个继承,因此可以使用接口来允许使用不同的类型。这也是允许您公开使用私有成员而无需进行反思(显式实现)的一种方法,因此它是在保持对象模型整洁的同时实现功能的一种好方法。
例如:
public interface IExample
{
void Foo();
}
public class Example : IExample
{
// explicit implementation syntax
void IExample.Foo() { ... }
}
/* Usage */
Example e = new Example();
e.Foo(); // error, Foo does not exist
((IExample)e).Foo(); // success
我认为您需要对设计模式有充分的了解,以便了解其中的功能。