【问题标题】:Using The Interface Methods I Want Based On The Implementation根据实现使用我想要的接口方法
【发布时间】:2011-01-24 13:31:08
【问题描述】:

我有两个与接口相关的基本概念,我需要更好 对此事的认知。

1) 如果我只想使用接口的部分,如何使用接口 给定类中的方法?例如,我的 FriendlyCat 类继承自 Cat 并实现 ICatSoundsICatSounds 暴露了 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();

如果答案是,正如我假设的那样,该方法没有 代码,因此什么也不做。然后,如果我想要这些方法 行为与接口所针对的类中的方法完全相同 被命名了,我该怎么办?

再次提前感谢...

【问题讨论】:

  • 坦率地说,我会质疑 FriendlyCat 是否应该有一个“MakePurr”方法。我的回复有更多细节。如果我有什么不清楚的地方,我可以编写一个示例实现。

标签: c# interface


【解决方案1】:
  1. 您必须在界面中实现所有方法。创建两个接口,IHappyCatSounds 和 IMeanCatSounds,拆分出这些方法。不要在 FriendlyCat 中实现 IMeanCatSounds,因为友好的猫不是卑鄙的猫。您必须将接口视为合约。当您编写接口时,您保证实现该接口的每个类都将具有这些成员。

  2. 它会引发 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,并完整阅读。它很短,但它应该可以帮助您理清问题。

不过,对于初学者,我会阅读 thisthis

【讨论】:

  • 谢谢大卫,我现在得到了#1,但请你看看我对#2 的编辑。我这里还是有问题。
  • 是的,你的理解是错误的。接口不是类的完全设计方法的反映。它只不过是一份合同或一份规范。就像有人说“给我造一辆左转的车”。太好了,您可以“说”左转,但您需要实际实现左转的方法。接口定义了类“将做什么”,实现类中的代码定义了“它将如何做”。很可能它不会通过抛出异常来完成猫的咕噜声。您是否阅读了我在上面发布的链接,尤其是第二个?
  • 谢谢大卫。我昨天读了第一篇,但觉得它并没有真正填补我的空白。我现在正在阅读第二本,它更有帮助。如果我有其他问题,我会在完成后跟进。再次感谢。
【解决方案2】:

接口是一种契约。您必须至少为所有方法提供存根。因此,设计一个好的接口是在拥有大量小接口(因此必须使用其中的几个来完成任何事情)和拥有大型复杂接口之间的平衡行为,您只使用(或实现)其中的一部分。如何选择没有硬性规定。

但您确实需要记住,一旦您发布了您的第一个版本的代码,更改您的界面就会变得非常困难。很多。在设计它们时,最好至少提前一点考虑。

至于实现,很常见的代码会存根尚未编写的方法,并引发 NotImplemented 异常。在大多数情况下,您并不想真正发布 NotImplemented,但它可以很好地解决由于您尚未实现接口的必需部分而无法编译代码的问题。

【讨论】:

    【解决方案3】:

    在一个类中“故意”不实现所有接口契约的框架中至少有一个例子: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&lt;T&gt; 的用户看不到智能感知中的 Insert 方法 - 只有先将其转换为 IList&lt;T&gt;,他们才能看到它。

    必须这样做确实暗示您的界面层次结构有点混乱并且需要重构,但如果您无法控制界面(或需要向后兼容性,这可能是 MS 决定采用框架中的这条路线)。

    【讨论】:

    • 有趣。为什么投反对票?我并不是说 dushkin 应该 这样做 - 我只是指出 MS 在框架中自己做。如果他无法控制他需要实现的接口,这绝对是一个选择。
    • (我没有投反对票)。实际上,我在此评论中指出了您最后一段的大部分内容,但将其删除。我同意你的看法。我什至认为对您的回复投反对票的唯一原因是建议更加强调“除非您别无选择,否则不要这样做”。我个人认为没有任何解释的投票是粗鲁的,所以我要给你投票(因为你的信息是正确的)。我同意 - 如上所述,问题是接口/继承层次结构混乱的症状。
    • 另外,+1 用于指出显式接口实现,这是您在这种情况下应该做的最小值。方法你必须实现,但至少你可以尽可能的隐藏它!
    • 谢谢 kyoryu。显式实现是我在试图弄清楚为什么 ReadOnlyCollection 没有 Insert 方法但仍然实现 IList 时才真正发现的功能! :)
    • 有趣的是,我在创建 ReadOnlyCollection(特别是 ReadOnlyObservableCollection)相似对象时做了完全相同的事情。
    【解决方案4】:

    一些想法:

    1. 接口分离原则。接口应该尽可能小,并且只包含不能分开的东西。由于MakePlayfulMeow()MakeHiss() 本质上并未绑定在一起,因此它们应该位于两个单独的接口上。​​

    2. 您遇到了深度继承树的一个常见问题,尤其是您所描述的继承类型。即,通常有三个对象具有三种不同的共同行为,只是它们都不共享同一个集合。所以Lion 可能Lick()Roar()Cheetah 可能Meow()Lick(),而AlienCat 可能Roar()Meow()。在这种情况下,没有明确的继承层次结构是有意义的。由于此类情况,将行为分成单独的类,然后创建组合适当行为的聚合通常更有意义。

    3. 考虑这是否是正确的设计。您通常不会告诉猫发出咕噜声,而是对它做一些导致它发出咕噜声的事情。因此,与其将MakePlayfulMeow() 作为猫上的方法,不如在猫上设置Show(Thing) 方法更有意义,如果猫看到Toy 对象,它可以决定发出适当的声音。换句话说,不要将您的程序视为操纵对象,而应将您的程序视为对象之间的一系列交互。在这种类型的设计中,界面最终看起来不像“可以操作的东西”,而更像是“对象可以发送的消息”。

    4. 考虑更接近于数据驱动、可发现的方法,而不是更静态的方法。而不是Cat.MakePlayfulMeow(),使用Cat.PerformAction(new PlayfulMeowAction()) 之类的东西可能更有意义。这提供了一种更通用的接口的简单方法,该接口仍然可以被发现 (Cat.GetPossibleActions()),并有助于解决深层继承层次结构中常见的一些“Lions can't purr”问题。

    5. 另一种看待事物的方式是不必让接口与类定义 1:1 匹配。考虑一个类来定义什么是,以及一个接口来描述它的能力。所以FriendlyCat 是否应该继承某些东西是一个合理的问题,但它暴露的接口应该是对其能力的描述。这与我在第三点中建议的“接口作为消息声明”的想法略有不同,但并非完全不相容。

    【讨论】:

    • 嗨,Kyoryu。感谢您的反馈,非常感谢。我认为您的反馈对我提供的场景很有意义(实际上,“喵喵”更像是一种反应而不是命令/响应)。但是,我正在使用 cat 场景来帮助我理解一些更基本的概念,也许我没有选择最好的例子。我想我现在已经掌握了#1,但仍然需要根据我的编辑对#2 进行澄清。这可能是基本的,但我只是在学习。非常感谢您的意见。
    • @dushkin:在我看来,接口应该反映一个类的所有方法。它应该是两个类用来通信的消息的反映,所以如果一个类有多个“逻辑”端点,它可能会实现多个接口,每个接口都设计为供不同的消费者使用。虽然一个类可能会以这种方式结束,但在接口设计中应注意确保每个接口都是原子的,并且只将那些真正应该绑定在一起的东西绑定在一起。
    • @dushkin:如果 'Purr' 和 'Meow' 并不总是一起出现,那么它们不应该是一个单一的界面。如果他们碰巧,那就太好了,创建另一个继承两者的接口。但是,除非您可以保证这对所有 案例都是正确的,否则请确保您留下选择退出的方法。所以Read()Write() 不一定绑定在一起,因为某些内容可能是只读的。但是,至少在某些情况下,Add()Delete() 可能是。
    • @dushkin:现在,在许多情况下,您会想要一个包含所有这四种方法的接口——好吧,由较小的接口组成。一般来说,如果你考虑类/界面设计而不是“我能粘在一起多少”,而是“什么是不可能分开的?”,你可能会发现事情容易得多。此外,如果您的接口是由接口的消费者而不是消费者设计的,那么它们就可以很好地作为实现者必须支持的合同。对象的使用者不需要接口来执行此操作 - 他们已经可以看到该对象!
    【解决方案5】:

    想象你可以“挑选”。例如,假设您被允许不在 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 的用户不能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-10
      • 1970-01-01
      • 1970-01-01
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-25
      相关资源
      最近更新 更多