我有两个与界面相关的基本概念,我需要更好 对此事的认知。
1)如果我只想使用接口的某些,我如何使用接口 给定类中的方法?例如,我的 FriendlyCat 类继承自 Cat 并实现 ICatSounds 。 ICatSounds 公开 MakeSoftPurr()和 MakeLoudPurr()和 MakePlayfulMeow()。但是,它也暴露了 MakeHiss() 和 MakeLowGrowl() - 这两个我不需要我的 FriendlyCat 类。
当我尝试仅实现接口公开的方法的一些时 编译器抱怨其他人(我不需要)没有 实现。
这个答案是我必须创建一个只包含的界面 我想要公开的方法?例如,从我的 CatSounds 类中,我 会创建 IFriendlyCatSounds 吗?如果这是真的,那么当发生时会发生什么 我想在另一种情况下使用其他方法?我需要创建吗? 另一个定制的界面?这对我来说似乎不是一个好设计。
似乎我应该能够创建一个包含所有内容的接口 相关方法( ICatSounds )然后挑选我选择的方法 我正在使用基于实现的方法( FriendlyCat )。
2)我的第二个问题是非常基本的,但仍然是一个混乱的问题 我。当我实现界面(使用Shift + Alt + F10)时,我得到了界面 “throw new NotImplementedException();”的方法在身体里。什么 除了引用接口方法之外,我还需要做什么 我想在课堂上公开?我相信这是一个很大的概念哎呀,但是 类似于从基类继承,我想获得对方法的访问 通过接口暴露而不添加或更改它们。是什么 编译器期待我实现?
- 编辑 -
我现在明白#1,谢谢你的回答。但我还需要进一步阐述 在#2上。我最初的理解是界面是完全反映的 设计给定类的方法。那是错的吗?所以,如果ICatSounds有 MakeSoftPurr()和MakeLoudPurr(),然后存在这两个函数 CatSounds并做他们所暗示的。然后这个:
public class FriendlyCat: Cat, ICatSounds
{
...
public void ICatSounds.MakeLoudPurr()
{
throw new NotImplementedException();
}
public void ICatSounds.MakeSoftPurr()
{
throw new NotImplementedException();
}
}
实际上反映了已经存在的代码,为什么会这样 我实施什么?为什么我不能这样做:
FriendlyCat fcat = new FriendlyCat();
fcat.MakeSoftPurr();
如果答案是,正如我所假设的那样,该方法没有 代码因此无能为力。然后,如果我想要这些方法 行为与接口所在的类中的方法完全相同 被命名,我该怎么办?
提前再次感谢...
答案 0 :(得分:2)
界面是合约。您必须至少为所有方法提供存根。因此,设计一个良好的接口是一个平衡的行为,它具有许多小接口(因此必须使用其中的几个来完成任何事情),并且具有大型,复杂的接口,您只能使用(或实现)部分接口。对于如何选择没有严格的快速规则。
但是你需要记住,一旦你发布了第一个版本的代码,它就会变得更难以改变你的界面 lot 。在设计它们时,最好至少考虑一点。
至于实现,看到存储未编写的方法的代码并抛出NotImplemented异常是很常见的。在大多数情况下,你真的不想发布NotImplemented,但它很好地解决了没有代码编译的问题,因为你还没有实现接口的必需部分。
答案 1 :(得分:2)
在“故意”框架中至少有一个例子没有在类中实现所有接口的契约:ReadOnlyCollection<T>
由于这个类实现了IList<T>
,它必须有一个“Insert”方法,这在只读集合中没有任何意义。
微软实施它的方式非常有趣。首先,他们明确地实现了这个方法,如下所示:
public class ReadOnlyCollection<T> : IList<T>
{
public void IList<T>.Insert(int index, T item)
{
throw new NotSupportedException();
}
/* ... rest of IList<T> implemented normally */
}
这意味着ReadOnlyCollection<T>
的用户在智能感知中看不到插入方法 - 只有先将其转换为IList<T>
才会看到它。
必须这样做真的是一个提示,你的界面层次结构有点乱,需要重构,但如果你无法控制接口(或者需要向后兼容性,这可能是MS决定采取的原因)它是一个选项这条路线在框架中。)
答案 2 :(得分:1)
您必须在界面中实现所有方法。创建两个接口,IHappyCatSounds和IMeanCatSounds,拆分出这些方法。不要在FriendlyCat中实现IMeanCatSounds,因为友善的猫不是卑鄙的猫。您必须将接口视为合同。编写接口时,您可以保证实现接口的每个类都具有这些成员。
它会抛出NotImplementedException,因为您尚未实现它。编译器期望您实现在猫呜咽,喵喵声或嘶嘶声时完成的代码。接口中没有代码。它只不过是任何实现它的类的契约,所以你不能真正“访问接口实现的代码”,因为接口没有实现任何代码。从接口继承时,您实现代码。
例如:
// this is the interface, or the "contract". It guarantees
// that anything that implements IMeowingCat will have a void
// that takes no parameters, named Meow.
public class IMeowingCat
{
void Meow();
}
// this class, which implements IMeowingCat is the "interface implementation".
// *You* write the code in here.
public class MeowingCat : IMeowingCat
{
public void Meow
{
Console.WriteLine("Meow. I'm hungry");
}
}
我强烈建议您选择The Object Oriented Thought Process的副本,并完整阅读。这很简短,但它可以帮助你清理。
答案 3 :(得分:1)
想象一下,你可以“挑选。”例如,假设您被允许不在FriendlyCat上实现ICatSounds.MakeHiss()。现在,当您的类的用户写下以下代码时会发生什么?
public ICatSounds GetCat()
{
return new FriendlyCat();
}
ICatSounds cat = GetCat();
cat.MakeHiss();
编译器必须让这个传递:毕竟,GetCat返回一个ICatSounds,它被分配给一个ICatSounds变量,而ICatSounds有一个MakeHiss方法。但是当代码运行时会发生什么? .NET发现自己调用的方法不存在。
如果允许发生这将是不好的。因此编译器要求您实现接口中的所有方法。如果您愿意,您的实现可以抛出异常,例如NotImplementedException或NotSupportedException:但方法必须存在;运行时必须能够至少调用它们,即使它们爆炸了。
另见Liskov Substitution Principle。基本上,我们的想法是,如果FriendlyCat是ICatSounds,则必须在使用ICatSounds的任何地方进行替换。没有MakeHiss方法的FriendlyCat是不可替代的,因为ICatSounds的用户可以使用MakeHiss方法,但是FriendlyCat的用户不能。
答案 4 :(得分:0)
一些想法:
接口分离原理。接口应尽可能小,并且只包含无法分隔的内容。由于MakePlayfulMeow()
和MakeHiss()
并非本质上捆绑在一起,因此它们应位于两个不同的界面上。
您遇到了深度继承树的常见问题,尤其是您所描述的继承类型。也就是说,通常有三个对象具有三种不同的共同行为,但它们中没有一个共享同一组。因此,Lion
可能Lick()
和Roar()
,Cheetah
可能Meow()
和Lick()
,AlienCat
可能Roar()
}和Meow()
。在这种情况下,没有明确的继承层次结构是有意义的。由于这些情况,将行为分成单独的类,然后创建组合适当行为的聚合通常更有意义。
无论如何,请考虑这是否是正确的设计。你通常不会告诉猫咕噜声,你做了一些让它发出咕噜声的东西。因此,而不是MakePlayfulMeow()
作为猫的方法,也许在猫上使用Show(Thing)
方法更有意义,如果猫看到Toy
对象,它可以决定发出适当的声音。换句话说,不要将您的程序视为操纵对象,而应将您的程序视为对象之间的一系列交互。在这种类型的设计中,界面通常看起来不像“可以操作的东西”,更像是“对象可以发送的消息”。
考虑更接近数据驱动的,可发现的方法而不是更静态的方法。有Cat.MakePlayfulMeow()
之类的内容可能更有意义,而不是Cat.PerformAction(new PlayfulMeowAction())
。这为一个更通用的接口提供了一种简单的方法,它仍然可以被发现(Cat.GetPossibleActions()
),并且有助于解决深度继承层次结构中常见的一些“Lion
无法发出的问题”。
另一种看待事物的方法是不要使接口必须与类定义1:1匹配。考虑一个类来定义是什么,以及一个界面来描述它的功能。因此,FriendlyCat
是否应该从某些东西继承是一个合理的问题,但它所公开的接口应该是对其能力的描述。这与我在第三点中建议的“作为消息声明的接口”的概念略有不同,但并非完全不兼容。