我试图更好地理解C#和其他OOP语言中的接口。界面有什么作用?为什么需要它?我知道c#和Java不允许多重继承。大多数书籍都说接口是绕过单继承限制并允许不同类具有通用功能的一种方法。接口只是定义方法并强制类实现它们。为什么不让类本身定义和实现方法而不处理接口?例如:
4: using System;
5:
6: public interface IShape
7: {
8: double Area();
9: double Circumference();
10: int Sides();
11: }
12:
13: public class Circle : IShape
14: {
15: public int x;
16: public int y;
17: public double radius;
18: private const float PI = 3.14159F;
19:
20: public double Area()
21: {
22: double theArea;
23: theArea = PI * radius * radius;
24: return theArea;
25: }
.
.
.
为什么Circle类不能定义和实现Area(),Circumference()和Sides()方法本身?如果square类继承了IShape,则Circumference()方法必须是未实现的。我对接口的理解是否有所作为?
答案 0 :(得分:5)
当你想说“我不在乎你怎么做,但这就是你需要完成的事情”时,接口就是用来做的。有关详细说明,请参阅此link。
答案 1 :(得分:2)
C#中的对象可能具有实际上是不同类别的组合的函数;一个典型的例子就是教师的例子:
在这个例子中,教师具有一个人的特征(例如眼睛颜色)(虽然我有一些教师可能会打破这个例子)和一个雇员的特征(例如薪水)
C#不允许多重继承,因此我们使用接口来查看composition的概念。它通常用这种方式描述:
继承意味着如果Cat继承自Animal,那么Cat“is-a”Animal
组合意味着如果Cat实现了Noise,那么Cat“有一个”噪声
为什么这种区别很重要?好吧,想象我们的猫。我们实际上可以让Cat继承自Feline,后者继承自Animal。有一天,我们决定支持其他类型的Animal,所以我们决定修改Animal,但后来我们意识到我们将把这些更改推送到其他所有子类型。我们的设计和层次结构突然变得非常复杂,如果我们没有正确开始,我们必须对我们所有的子类进行广泛的重新设计。
规则是Prefer Composition over Inheritance,但请注意使用Prefer这个词 - 这很重要,因为它并不意味着我们应该只使用合成,而是我们的设计应该考虑继承是否有用,当组合也有用时。它还提醒我们,在基类中坚持使用每种可能的方法都是一个坏主意。
我喜欢你的形状示例。考虑:
我们的ShapeSorter可能有这样的方法:
public bool Sort(Shape shape)
{
foreach(Hole hole in Holes)
{
if(Hole.Type == HoleType.Circular && shape is Circle)
{
return true;
}
if(Hole.Type == HoleType.Square && shape is Square)
{
return true;
}
if(Hole.Type == HoleType.Triangular && shape is Triangle)
{
return true;
}
}
return false;
}
或者,我们可以做一些轻微的控制反转:
public bool Sort(Shape shape, Hole hole)
{
return hole.Accepts(shape); //We end up pushing the `if` code into Hole
}
或其某些变体。我们已经编写了很多代码,这些代码依赖于我们知道Shape的确切类型。想象一下当你有其中一个时,维持它是多么乏味:
所以,相反,我们自己思考 - 是否有更通用的方法可以通过将其提炼到相关属性来描述我们的问题?
你称之为IShape:
public interface IShape{
double Area {get;}
double Perimeter { get; } //Prefer Perimeter over circumference as more applicable to other shapes
int Sides { get; }
HoleType ShapeType { get; }
}
我们的Sort方法将成为:
public Hole Sort(IShape shape)
{
foreach(Hole hole in Holes)
{
if(hole.HoleType == shape.ShapeType && hole.Area >= shape.Area)
{
return hole;
}
}
return null;
}
看起来更整洁,但实际上并不是通过Shape直接完成的任何事情。
事实是,没有真相。最常见的方法将涉及使用继承和组合,因为现实世界中的许多东西都是一种类型,并且还具有界面最佳描述的其他属性。要避免的最重要的事情是在基类中坚持使用每个可能的方法,并使用巨大的if语句来计算派生类型可以做什么和不能做什么 - 这是很多难以维护的代码。此外,在基类中放置太多功能可能会导致您的代码具体设置 - 由于所有潜在的副作用,您不希望稍后修改内容。
答案 2 :(得分:1)
在这种情况下,是的,你绝对可以做到。但有些情况如mouse listeners
是接口。在它们中声明了伪方法。稍后作为开发人员在您的程序中,您可以覆盖这些方法并实现自己的自定义逻辑。
源:MouseListener接口定义了五种方法:mouseClicked,mouseEntered,mouseExited,mousePressed和mouseReleased。它们都是void方法,并将一个MouseEvent对象作为其参数。请记住,您必须定义所有这五种方法;否则,您的类必须声明为抽象。 MouseListener接口不会跟踪鼠标的显式移动等事件。
答案 3 :(得分:1)
为什么Circle类不能定义和实现Area(), Circumference()和Sides()方法本身?
它可以,但它将独立于界面。接口的主要优势之一是“运行时绑定”。
对于您当前的示例,您只有一个类Circle
,假设您创建了另一个类Rectangle
,并且您的两个类都实现了IShape
。稍后在运行时的代码中,您可以创建Circle
和Rectangle
的对象,并将其放在实例类型变量中。
IS形状;
if(用户需要一个圆圈)//只是一个示例测试来显示运行时绑定。 shape = new Circle(); 其他 shape = new Rectangle();
现在当你做
double area = shape.Area();
您将获得形状区域。在运行时,您将无法知道Rectangle
或Circle
是否需要区域。但是接口对象将调用它所持有的形状的实现方法。
答案 4 :(得分:1)
接口保证对象实现一组特定的方法或属性。是的,您的形状类可以在没有接口的情况下实现这些方法,但是没有办法告诉其他代码它实现它们,并且您可能会意外忘记实现它或更改其参数。
接口是一个定义。它允许其他代码知道定义是什么,而不知道实现是什么。换句话说,并不是圆圈不能实现这些方法,而是如果一个圆圈实现了界面,它就会保证它实现这些方法。
例如,您可能有一个名为IWriter的接口。 IWriter有一种方法:
public interface IWriter
{
void Write(string s);
}
注意那是多么通用。它没有说它写的是什么,或者它是如何写的......只是它写的。
然后,您可以拥有实现IWriter的具体方法,例如MemoryWriter,ConsoleWriter,PrinterWriter,HTMLWriter等......每个方法都以不同的方式写入,但它们都只用一种方法实现相同的简单接口。
public class ConsoleWriter : IWriter
{
public void Write(string s) {
Console.WriteLine(s);
}
}
public class MemoryWriter : IWriter
{
public void Write(string s) {
// code to create a memory object and write to it
}
}
您可以使用基类完成相同的操作,但这将依赖于实际的实现,并会创建您可能不需要的依赖项。接口将实现与定义分离。
关于没有圆周的方块和没有边的圆...这只是意味着你的IShape定义设计得不好。您正在尝试使对象适合可能不适用的单个定义。
答案 5 :(得分:1)
接口也称为合约,将在两个或多个类之间的交互过程中使用。
在这种情况下,当一个类被称为实现IShape
时,调用者将知道该类具有合同中定义的所有方法IShape
。来电者不会担心该课程是Square
/ Rectangle
/ Circle
。
回答你的问题
如果一个方形类继承了
IShape
,那么Circumference()
方法 将不得不实现。
您需要设计接口,使其足够通用。在这种情况下,接口应该有一个名为Perimeter的方法而不是圆周。
对于sides属性,我相信圆圈应该返回一个预定义的常量,例如int.MaxValue来表示无穷大。
要了解界面的好处,您需要了解调用者如何调用这些方法。
例如
public double DisplayArea(IShape shape)
{
Console.WriteLine(shape.Area().ToString());
}
您可以通过
调用上述方法//Code to create a Circle
this.DisplayArea(circle);
//Code to create a Square
this.DisplayArea(square);
这是可能的,因为DisplayArea
知道IShape
类型的任何对象都会有一个名为Area
的方法,它会返回double
。所以,它根本不用担心类名。将来如果您实现Ellipse
类并使其实现接口IShape
,DisplayArea
方法将无需任何修改即可使用。
答案 6 :(得分:1)
当然,您可以自己添加方法,而无需实现接口。但是接口确保您不会将其留给您的愿望。这就像义务减去仲裁。如上所述,界面是合同,必须履行,你应该如何实现。
答案 7 :(得分:1)
在现实世界中,你不仅会在圈子里工作。您将需要其他形状,例如矩形/正方形等。接口允许您定义一些您将在应用程序中使用的常见合约,然后您可以轻松添加新形状。在添加新形状时,您不会被迫大幅更改代码 - 您只需实现IShape界面即可完成(嗯,需要进行一些更改,但不是那么多)。
答案 8 :(得分:0)
实现接口和继承之间的区别在于继承指定了“是”行为,如“A'是'也是B'(猫'是'也是动物)。接口实现了更多” “(或者是)行为。(一只鸟'有'翅膀,飞机也有'翅膀'。这是大致的简化,但你明白了。
这就是为什么在这些语言中你只能从一个对象继承而是实现许多接口。界面提供了更多的Mixin功能(它可以让你混合各种各样的东西,就像艺术家混合油漆一样)。
继承或接口都不比另一个好。您真的需要设计对象结构,以便使用正确的结构。
例如。您不会仅仅因为它们都可以飞行而从Bird类继承您的Airplane类。是的,飞机和鸟有很多共同点,但飞机不是鸟。 (已经说过你的特定应用程序中的语义可能不同,你可能不会做出这种区分,或者你可能永远不必在你的应用程序中处理飞机)。在这里你会有一个类似
的界面public Interface ICanFly
{
void Fly();
void Land();
}
通过这种方式,飞机和鸟类都可以实现ICanFly界面,而无需假装飞机是一只鸟。您可能还有其他一些类只关心某些东西是否飞行,无论它是人造的还是自然的。假设你有一个带有Track方法的Radar类
public class Radar
{
// This method doesn't care whether the object is a bird or plane.
public void Track(ICanFly flyingObject)
{
}
}
你可以拥有其他专门适用于动物的课程
public class MigrationTracker
{
// I only deal with birds since aeroplanes don't migrate.
public void Track(Bird birdsToTrack)
{
}
}
正如您所看到的,Interfaces和Inheritance在您设计类的方式上提供了不同的选项,但您可以明智地使用它们。例如,您对本地动物园的应用可能永远不必处理aeorplanes,在这种情况下,飞行功能可以添加到鸟类并继承而不是与接口混合。
答案 9 :(得分:0)
为什么不让类本身定义和实现方法 处理界面?
想象一下Circle类,方法名称为AreaOfCircle
矩形方法名称是' AreaOfRectangle'
Square没有像' Area'
这样的方法除上述讨论外,方法名称没有统一性。无法保证在所有这些类中实施重要方法。
需要花费大量精力来验证所有类中的重要方法。 如果实现了Interface,那么只需找到他对Interface的引用,就可以保证必须实现那些具有相同名称的方法。
因此保证了接口合同。 此外,在继承类中实现任何新的接口契约也非常容易。
如果一个square类继承了IShape,那么Circumference()方法 将不得不实现。
将所有签名/合同放在单个界面中是错误的做法。当我知道方形和矩形等多边形不能实现Circumference()时。我会将Circumference()放在不同的界面中。
因此,界面可以帮助您组织代码,因此有助于实现各种设计模式。