禁止现有类的接口实现是否有任何优势?

时间:2014-08-11 19:03:02

标签: c# oop haskell interface programming-languages

在静态OOP语言中,使用接口来声明几个类共享一些逻辑属性 - 它们是一次性的,可以与int进行比较,可以序列化等等。

让我们说.net没有标准的IDisposable界面,我刚刚提出了这个美好的想法:

interface IDiscardable { void Discard(); }

我的应用使用了很多System.Windows.Form,我认为Form满足成为IDiscardable的逻辑要求。问题是,Form是在我的项目之外定义的,因此C#(和JavaC++ ...)不允许我实施{{ 1}}为了它。 IDiscardable并不允许我正式表示C#可以是Form这一事实(而且我可能会以discarded包装类结束什么的。

相比之下,MyForm具有Haskell,它在逻辑上类似于接口。 typeclasses实例可以作为字符串呈现(或序列化),Show允许进行比较等。但是有一个至关重要的区别:您可以编写类型类实例(类似于实现接口)而不访问类型的源代码。因此,如果Eq为我提供了某种Haskell类型,则为其编写Form实例非常简单。

我的问题是:从语言设计师的角度来看,第一种方法有什么优势吗? Discardable不是面向对象的语言 - 第二种方法是否以任何方式违反了Haskell

谢谢!

4 个答案:

答案 0 :(得分:6)

这是一个困难的问题,源于一个常见的误解。 Haskell类型类(TC)被称为与面向对象编程语言中的接口或抽象类(IAC)“逻辑相似”。他们不是。它们代表了关于类型和编程语言的不同概念:IAC是子类型的一种情况,而TC是参数多态的一种形式。

然而,既然你的问题是方法论的,我在这里从方法论的角度回答。从第二个问题开始:

  

第二种方法[扩展类外实现的方法]是否以任何方式违反OOP

面向对象编程是一组描述程序执行的想法,执行的主要元素,如何在程序代码中指定这些元素,以及如何构造程序以便分离不同的规范元素。特别是,OOP基于这些想法:

  • 在任何执行状态下,进程(执行程序)由一组对象组成。这个集合是动态的:它可能包含不同状态的不同对象,通过对象创建和销毁。
  • 每个对象都有一个由一组字段表示的内部状态,其中可能包含对其他相关对象的引用。关系是动态的:同一对象a的相同字段可能在不同的状态指向不同的对象。
  • 每个对象都可以从另一个对象接收一些消息。在接收到消息时,对象可以改变其状态并且可以向其字段中的对象发送消息。
  • 每个对象都是的一个实例:该类描述了对象具有哪些字段,可以接收哪些消息以及接收消息时它所执行的操作。
  • 在对象a中,相同的字段a.f可能在不同的状态指向 不同的对象,可能属于不同的类。因此,a 不需要知道这些对象b属于哪个类;只有需要知道这些对象接受的消息。因此,这些字段的类型可以是接口。 接口声明对象可以接收的一组消息。该类明确指定该类的对象满足哪些接口。

我对这个问题的回答:在我看来是的。

在类外部实现接口(如示例中所示)会破坏以下一个想法:对象的类描述该类中的对象可以接收的完整消息集。

但是,您可能想知道,这是(部分)与AspectJ中的“方面”有关。 Aspect描述了几个类中某个“方法”的实现,并且这些实现被包含( weaved )到类中。

回答第一个问题,“第一种方法是否有任何优势”,答案也是肯定的:对象的所有行为(它回答的消息)仅在一个地方描述,上课。

答案 1 :(得分:5)

嗯,Haskell方法确实有一个缺点,例如,当您编写两个不同的库时,每个库都为相同的外部类型提供自己的接口Foo实现(由第三个库提供) )。在这种情况下,现在这两个库不能在同一程序中同时使用。因此,如果你称缺乏劣势是一种优势,那么我认为这对于OOP语言这样做是一个优势 - 但这是一个非常弱的优势。

然而,我要添加的是Haskell类型类有点类似于OOP接口,但并不完全像它们。但类型类有点像策略和模板方法模式;可以通过显式传递提供类类操作实现的“字典”对象来模拟类型类。所以下面的Haskell类型类:

class Monoid m where
    mempty :: m
    mappend :: m -> m -> m

...可以使用此显式字典类型进行模拟:

data Monoid_ m = Monoid_ { _mempty :: m, _mappend :: m -> m -> m }

...或像这样的OOP界面:

interface Monoid<M> {
    M empty();
    M append(M a, M b);
}

在这之上添加什么类型的类是编译器将隐式地维护和传递你的字典。有时在Haskell社区中,您会得到关于何时以及类型类是否优于显式字典传递的论据;例如,参见Gabriel Gonzalez的"Scrap your type classes"博客文章(请记住,他并不是100%同意他在那里所说的内容!)。因此,这个想法的OOP对应的不是扩展语言以允许外部implements声明,只是明确使用策略或模板方法有什么缺点?

答案 2 :(得分:2)

您所描述的是适配器模式。在新类型中组合对象的行为,它为底层类型提供了一些额外的行为,在这种情况下是另一个接口的实现。

与许多设计模式一样,不同的语言选择不同的设计模式直接融入语言本身并提供特殊的语言支持,通常采用更简洁的语法形式,而其他模式则需要通过使用来实现没有自己特殊语法的其他机制。

C#对适配器模式没有特殊的语言支持,您需要创建一个新的显式类型,它构成您的其他类型,实现接口,并使用组合类型来实现接口的契约。他们是否有可能在语言中添加这样的功能,当然。与现有的任何其他功能请求一样,它需要进行设计,实施,测试,记录,并考虑各种其他费用。此功能(迄今为止)尚未完成剪切。

答案 3 :(得分:1)

你所描述的被称为 duck typing ,在短语“如果它像鸭子一样走路,像鸭子一样游动,像鸭子一样呱呱叫,那就是鸭子”。

C#实际上允许通过dynamic关键字进行动态(运行时)鸭子输入。它不允许的是静态(编译时)duck typing。

你可能需要微软的某些人来提供C#中不存在的确切原因,但这里有一些可能的候选人:

  • 添加功能的"minus 100 points"理念。功能不仅没有缺点,为了证明在实现,测试,维护和支持语言功能方面的努力是合理的,它必须提供明显的好处。在dynamic关键字和适配器模式之间,有很多情况下这是有用的。反射也足够强大,可以有效地提供鸭子打字,例如我相信使用Castle的DynamicProxy是相对简单的。

  • 在某些情况下,您希望类能够指定访问方式。例如,流畅的API通常通过使用接口来控制类的链接方法的有效排序和组合。例如,请参阅this article。如果我的流畅类是围绕一个语法设计的,该语法表明一旦调用方法A,除了B之外没有其他方法可以被调用,我可以通过以下接口控制它:

    public class FluentExample : ICanCallAB
    {
        public ICanCallB A()
        {
            return this;
        }
    
        public ICanCallAB B()
        {
            return this;
        }
    }
    
    public interface ICanCallA
    {
        void A();
    }
    
    public interface ICanCallAB : ICanCallA
    {
        void B();
    }
    

    当然,消费者可以使用强制转换或dynamic解决这个问题,但至少在这种情况下,该类可以表明自己的意图。

  • 与上述内容相关,接口实现是意义的声明。例如,TreePoodle可能都有Bark()成员,但我希望能够将Tree用作IDog