【问题标题】:Are Interfaces Compatible With Polymorphism接口是否与多态兼容
【发布时间】:2011-09-03 02:37:44
【问题描述】:

我在与多态类型(甚至多态接口)交互的接口概念上遇到了麻烦。我正在使用 C# 进行开发,并希望得到与此定义保持接近的答案,尽管我认为这仍然为每个人提供了足够的空间来提出答案。

举个例子,假设您想编写一个程序来绘制事物。您为绘制的actor定义一个界面,并为绘制的主题定义一个界面,此外,您还有一些可以以更具体的方式绘制的主题。

interface IPainter {
  void paint(IPaintable paintable);
}
interface IPaintable {
  void ApplyBaseLayer(Color c);
}
interface IDecalPaintable : IPaintable {
  void ApplyDecal(HatchBrush b);
}

我可以想象制作一个类似于以下内容的画家:

class AwesomeDecalPainter : IPainter
  {
    public void paint(IPaintable paintable) {
      IDecalPaintable decalPaintable = (IDecalPaintable)paintable;
      decalPaintable.ApplyBaseLayer(Color.Red);
      decalPaintable.ApplyDecal(new HatchBrush(HatchStyle.Plaid, Color.Green));
    }
  }

当然,如果paintable 没有实现IDecalPaintable,这将抛出。它立即引入了 IPainter 实现和它所操作的 IPaintable 之间的耦合。但是,我也认为说 AwesomeDecalPainter 不是 IPainter 只是因为它的使用仅限于 IPaintable 域的子集是没有意义的。

所以我的问题实际上有四个方面:

  • 接口是否兼容 多态性?
  • 好不好 设计实现一个IPainter 可以在 IDecalPaintable 上操作吗?
  • 如果可以独占怎么办 在 IDecalPaintable 上?
  • 是否有任何文献或源代码 举例说明接口和多态类型如何 应该互动吗?

【问题讨论】:

  • IDecalPainter.paint 是一个糟糕的设计的原因,请参阅"What is the Liskov Substitution Principle?"。你应该拥有的是一个重载的paint,它需要一个IDecalPaintable,以便AwesomeDecalPainter 可以同时接受IPaintables 和IDecalPaintables。
  • ... 一个IPainter 应该能够在任何IPaintablepaint。说AwesomeDecalPainter 不是IPainter 是完全有道理的,因为它不能在所有IPaintables 上运行。见circle-ellipse problem,也称为square-rectangle problem
  • 似乎不可能说 IPainter 应该能够在 some IPaintable 上进行绘画,但该语言直接促进了这种用法。也许接口可以安全地对多态类型进行操作,但接口本身不能是多态的。
  • 只要你支持IPaintable,你可以添加任何你想要的东西。您的界面所说的是,任何作为 IPainter 的人也可以在任何对 ApplyBaseLayer 是 IPaintable 的基板上进行操作。这意味着我可以让任何人出现,从贫民区的孩子到伦勃朗的学徒,只要他们是 IPainter,我可以给他们任何东西,从画布到砖墙,只要是 IPaintable,他们可以应用BaseColor。时期。他们还能做什么就是他们能做什么。它不是由 IPainter 和 IPaintable 定义的,作为我不认识或不关心的客户。
  • @SmokingRope:接口不是多态的,因为它们没有任何方法;他们只定义方法签名。它们支持多态性,因为实现相同接口的不同类可以有自己的方法实现,但都具有具有相同签名的相同方法。使用仅适用于 IDecalPaintableIPaintable 参数定义 paint 方法不仅会破坏 Liskov 替换原则,还会破坏类型系统。

标签: c# .net oop interface polymorphism


【解决方案1】:

类的接口是该类“用户”的工具。接口是类的公开表示,它应该向任何考虑使用它的人宣传哪些方法和常量可以从外部访问和访问。因此,顾名思义,它始终位于用户和实现它的类之间。

另一方面,抽象类是一种工具,旨在帮助扩展它的类的“实现者”。它是一种基础设施,可以对具体类的外观施加限制和指导。从类设计的角度来看,抽象类在架构上比接口更重要。在这种情况下,实现者位于抽象类和具体类之间,将后者构建在前者之上。

所以简单地回答你的问题,接口是代码要尊重的“合同”。当以这种方式使用时,它更适用于多态性的继承。

抽象类,它们定义了一个“类型”。当具体的子类使用抽象类并重新定义方法、添加新方法等时……你会看到多态性在起作用。

我知道这篇文章可能会让您更加困惑,直到我学会了设计模式,这对我来说才有意义。通过掌握一些简单的模式,您将更好地了解每个对象的作用以及继承、多态性和封装如何协同工作以构建设计简洁的应用程序。

祝你好运

【讨论】:

  • 同意!我读了《Head First design patterns》这本书,觉得他的书就是他们在本科课程中遗漏的一切。没有它,它只是你工具箱里的一堆工具,但你还没有胶带把它们放在一起。
【解决方案2】:

接口不仅与多态兼容——它们对它来说是必不可少的。您似乎缺少的部分想法是,如果有一个像 IPaintable 这样的接口,则期望实现它的每个对象都将提供一些默认的绘制方法,通常封装其他方法,这些方法将以某种有用的方式配置对象.

例如,IPaintable 接口可能会定义一个绘制方法:

无效油漆(ICanvas 画布);

请注意,Paint 方法没有说明应该绘制什么、什么颜色或其他任何内容。各种可绘制对象会暴露属性来控制这些东西。例如,Polygon 方法可能会处理 OutlineColor 和 FillColor 属性以及 SetVertices() 方法。想要绘制大量对象的例程可以接受 IEnumerable(Of IPaintable) 并简单地对所有对象调用 Paint,而不必知道如何绘制它们。一个对象是通过调用 Paint 来绘制的,除了应该在其上绘制它的画布之外没有任何参数,这一事实绝不会阻止 Paint 方法执行各种精美的渐变填充、纹理映射、光线跟踪或其他图形效果。控制绘画的代码对这些事情一无所知,但 IPaintable 对象本身会保存他们需要的所有信息,因此不需要调用代码来提供它。

【讨论】:

  • 这是最好的答案。
  • @Zak:很高兴你喜欢它。应用程序能够在不知道实际需要什么的情况下绘制某些东西的想法至少可以追溯到 Macintosh;我记得在 Macintosh II 上运行旧版本的 TeachText(如记事本,但能够显示嵌入在文档资源分支中的图片)当我打开一个包含彩色图片的文档并且它以彩色显示时。原来 Macintosh 图片支持...
  • ...不像第一次体验让我想的那样普遍(旧的应用程序将被限制为八种颜色),但我认为这个原则在这里适用。事实上,IPaintable 只公开了一个无聊的 Paint 方法,这并不会以任何方式限制 Paint 方法做的事情远远超出使用 IPaintable 的应用程序可能预期的任何事情的能力。
【解决方案3】:

问题实际上是您定义了一个模糊的接口,合同是一个更合适的术语。就像你进了麦当劳然后点了一个汉堡:

interface Resturant
{
    Burger BuyBurger();
}

接你订单的人一会看起来有点困惑,但最终他/她会为你提供任何汉堡,因为你没有指定你想要什么。

这里也一样。你真的只是想定义某些东西是可绘制的吗?如果你问我,这就像自找麻烦。始终尝试使接口尽可能具体。拥有几个小的特定接口总是比一个大的通用接口好。

但是让我们回到你的例子。在您的情况下,所有类只需要能够绘制一些东西。因此我会添加另一个更具体的接口:

    interface IPainter
    {
        void Paint(IPaintable paintable);
    }
    interface IDecalPainter : IPainter
    {
        void Paint(IDecalPaintable paintable);
    }
    interface IPaintable
    {
        void ApplyBaseLayer(Color c);
    }
    interface IDecalPaintable : IPaintable
    {
        void ApplyDecal(HatchBrush b);
    }

    class AwesomeDecalPainter : IDecalPainter
    {
        public void Paint(IPaintable paintable)
        {
            IDecalPaintable decalPaintable = paintable as IDecalPaintable;
            if (decalPaintable != null)
                Paint(decalPaintable);
            else
                paintable.ApplyBaseLayer(Color.Red);
        }

        public void Paint(IDecalPaintable paintable)
        {
            paintable.ApplyBaseLayer(Color.Red);
            paintable.ApplyDecal(new HatchBrush(HatchStyle.Plaid, Color.Green));
        }
    }

我建议您阅读 SOLID 原则。

更新

更完善的接口实现

    interface IPaintingContext
    {
        //Should not be object. But my System.Drawing knowledge is limited
        object DrawingSurface { get; set; }
    }
    interface IPaintable
    {
        void Draw(IPaintingContext context);
    }

    class AwesomeDecal : IPaintable
    {
        public void Draw(IPaintingContext paintable)
        {
            // draw decal
        }
    }

    class ComplexPaintableObject : IPaintable
    {
        public ComplexPaintableObject(IEnumerable<IPaintable> paintable)
        {
            // add background, border 
        }
    }

在这里您可以创建尽可能复杂的绘画项目。他们现在知道他们在什么上绘画,或者在同一表面上使用了哪些其他可绘画材料。

【讨论】:

  • 您是在建议 AwesomeDecalPainter 将自己降级为以不那么令人敬畏的方式进行绘画?在我看来,它违反了它的目的。
  • 不,看起来它的行为与接口定义的完全一样。问题不在于降级,而在于定义的接口。
  • 我添加了一个替代实现,可以更好地利用接口。 Context 接口可以有一些通用的绘画方法,从而可以在任何东西上绘画。
  • 在第二次修订中,IPaintable 接口仍仅限于完全按照 IPaintingContext 允许的方式进行绘制。 IPaintingContext 无法拥有另一个接口支持的派生 IAwesomePaintingContext。因此,接口与多态不兼容?
  • 没有。不对。 OOP 的问题是您必须按照合同描述的方式实现类/接口。因此,如果IAwesomePaintingContext 继承IPaintingContext 它应该按照IPaintingContext 的描述工作,如果一个对象只能使用IPaintingContext 而不能使用IAwesomePaintingContext。如果一个类不能使用IPaintingContext,它不应该接受它,而只能接受IAwesomePaintingContext。如果您无法做到这一点,则需要重构您的架构。
【解决方案4】:

我是否正确理解了您的问题?

我认为你的问题是接口是否可以行使继承的要领?

(即从父接口继承的子接口)。

1) 如果是这样,语法上肯定,为什么不呢?

2) 至于实用性,它可能没有什么价值,因为您的子界面本质上并没有重用您父界面中的任何东西。

【讨论】:

  • 我的问题非常广泛,接口“合同”能否指定多态行为。接口规范能否表现出多态行为。这显然是可能的,但这是个好主意吗?不过,我认为这个线程中的很多 cmets 都有助于阐明一些问题。
  • #SmokingRobe,我可能已经把你的问题变成了另一个问题,这是基于我对你问题的理解,并且我已经记录了它。 gray-suit.blogspot.com/2011/05/…至于回答这是不是一个好主意,我想先问一个。为什么继续使用接口而不仅仅是 Sup/子类化?如果您已经从一个地方继承,并且不能“直接”从另一个地方继承。然后,就像上面提到的帖子一样,您可能需要重构升技。
  • C# 接口的主要优点是多重继承。更一般地说,接口意味着两个实现者可以转换为同一个接口,而无需在正式的继承层次结构中关联实现。不相关的类型可以在不遵循 is-a 关系的情况下支持常见行为。
猜你喜欢
  • 2013-09-18
  • 1970-01-01
  • 1970-01-01
  • 2011-09-16
  • 2017-06-07
  • 2011-05-23
  • 2011-10-22
  • 2017-02-12
  • 1970-01-01
相关资源
最近更新 更多