【问题标题】:When to use interfaces in Dart?什么时候在 Dart 中使用接口?
【发布时间】:2012-05-08 16:33:42
【问题描述】:

我正在阅读 Dart 的文档,我有点困惑,可能是因为我来自 Ruby,关于如何使用接口。当然,接口并不是 Dart 独有的,关于何时应该使用接口有很多解释。例如,This one 似乎在说接口仅在团队中才有用。在开源世界中,每个人都在阅读和重用别人的代码,这意味着什么?

我看到的一个有趣的解释似乎暗示使用了接口:

  1. 在缺乏多重继承的语言中,以及
  2. 就此而言,它们以某种方式作为缺少多重继承的解决方法。

我不明白。我知道 Ruby 中的模块是一种解决方法,因为它们允许我用实际的主体定义真正的方法。接口只允许我定义实现它的类应该有哪些方法。有什么问题?谁能告诉我一个真正有用的例子,我可以立即看到使用接口的价值?

附:在相关说明中,有没有办法在 Dart 中使用多重继承?

【问题讨论】:

    标签: inheritance interface dart


    【解决方案1】:

    更新:the interface keyword has since been removed from Dart


    接口很有用,因为它们允许您切换类的实现,同时仍然允许验证传入的类型是否满足接口的要求。

    举以下(经常使用的)例子:

    interface Quackable {
      void quack();
    }
    

    这定义了将传递给方法的类的要求,例如:

    sayQuack(Quackable quackable) {
       quackable.quack();
    }
    

    允许您使用 Quackable 对象的任何实现,例如:

    class MockDuck implements Quackable {
      void quack() => print("quack");
    }
    
    class EnterpriseDuck implements Quackable {
      void quack() {
        // connect to three enterprise "ponds"
        // and eat some server bread
        // and say "quack" using an messaging system
      }
    
    }
    

    这两种实现都可以与 sayQuack() 函数一起使用,但其中一种实现所需的基础设施要少得多。

    sayQuack(new EnterpriseDuck());
    sayQuack(new MockDuck());
    

    我在 Java 世界中一直使用这种模式,在构建使用一些“企业鸭子”的解决方案时。在本地开发时,我只需要能够调用 sayQuack() 函数并返回一些硬编码的模拟数据。

    鸭子打字

    由于 Dart 是可选类型的,因此您实际上不需要使用接口,只需编写一个包含正确方法签名的类即可(尽管工具无法验证它)。

    class Person {   // note: no implements keyword
      void quack() => "I'm not a duck";
    }
    
    sayQuack(new Person()); // provides the quack method, so this will still work
    

    所有类都是接口

    最后,所有的类也是接口。这意味着即使第三方系统可能在没有使用接口的情况下编写,您仍然可以使用具体类,就好像它是一个接口一样。

    例如,想象以下企业库:

    class EnterpriseDuck { // note: no implements keyword
      void quack() {
        // snip
      }
    }
    
    sayQuack(EnterpriseDuck duck) {  // takes an instance of the EnterpriseDuck class
      duck.quack();
    }
    

    并且您想以类型检查器可以验证的方式将模拟鸭子传递给 sayQuack 方法。您可以创建自己的 mockDuck 来实现 EnterpriseDuck 隐含的接口,只需使用 EnterpriseDuck 作为接口:

    class MockDuck implements EnterpriseDuck {
      void quack() => "I'm a mock enterprise duck";
    }
    

    多重继承

    就多重继承而言,这在 Dart 中是不可能的。但是,您可以实现多个接口并提供您自己的所需方法的实现,例如:

    class MultiDuck implements Quackable, EnterpriseDuck, Swimable {
      // snip...
    }
    

    接口可以有默认类

    当你使用 Dart 时,你会发现大多数“类”实际上都是接口。 List、String 等都是提供默认实现的接口。当你打电话时

    List myList = new List();
    

    您实际上是在使用 List 接口,并且 new 关键字从接口重定向到底层默认 List 实现。

    关于团队发展

    接口在团队开发中非常有用,即使在开源世界中也是如此。该接口定义了您应该构建的方法和属性,以便您的组件与我的组件一起使用。您可以构建您自己的该接口的测试实现,我可以构建该接口的具体实现,当我们都完成后,我们可以集成。如果没有已发布的共享接口,我需要提供我的具体实现才能真正开始。

    希望有帮助!

    【讨论】:

    • 非常感谢克里斯,这正是我所需要的。此外,示例中的类名也很可爱。
    • 你的意思是,你的两个代码 cmets:'// note: no interface keyword' 而不是 '// note: no implements keyword'。
    • 考虑更新这篇文章,或者在这篇文章中添加评论,说明在 Dart 中不再使用 interface 关键字。
    • 您的 Person 示例不起作用。也许是因为我在 Dart2 上测试过
    【解决方案2】:

    基本上,接口与多重继承无关。这样做时可能会伪造多重继承和滥用接口,但如果你想要真正的多重继承(或混合或特征),那么 Dart 不提供它们(目前 - 我相信混合在某一天会找到它们的方式)。

    接口的好处是显式契约。假设您有两个组件 AB 需要相互配合。你当然可以直接从A 调用B 并且它会起作用,但是下次你想要更改B 时,你将不得不查看A 它是如何使用它的。这是因为B 没有公开显式接口。是的,接口的正确词不是implement,而是expose

    如果您将B 的实现隐藏在一个接口后面,并且只将该接口提供给A,那么您可以随意更改B,只需要担心仍然暴露相同的接口 .该接口甚至可以被多个类公开,调用者不必关心(甚至知道)。

    注意interface这个词在这里有两个意思:组件的通用contract,也可以在文档中用通俗的英文描述,以及一个特殊的语言结构,它可以帮助您在编程语言中描述(并强制执行)合同的某些部分。

    您不一定必须使用语言结构,但使用它来描述编程语言允许您描述的合同部分被认为是一种很好的风格。

    现在,这里的合同到底是什么?简而言之,契约是对组件对其用户的期望以及用户对组件的期望的描述。

    例如,假设我有一个计算数字绝对值的方法:

    class MathUtils {
      /// Computes absolute value of given [number], which must be a [num].
      /// Return value is also a [num], which is never negative.
      absoluteValue(number) {
        ... here's the implementation ...
      }
    }
    

    这里的合约在文档注释中有完整的描述(嗯,不完整,我们也可以描述什么是绝对值,但这已经足够好了)。嗯……不过有些部分的评论可以直接用语言表达吧?

    class MathUtils {
      /// Computes absolute value of given [number].
      /// Return value is never negative.
      num absoluteValue(num number) {
        ... here's the implementation ...
      }
    }
    

    注意合约的某些部分不能用编程语言表达——这里,语言不知道绝对值是什么,这需要留在评论中。此外,你不能表示返回值永远不会是负数,所以这也必须留在评论中。但事实上,您的代码的读者知道什么是绝对值(并且它永远不会是负数)并且方法名称非常清楚目的,因此可以完全省略注释:

    class MathUtils {
      num absoluteValue(num number) {
        ... here's the implementation ...
      }
    }
    

    所以现在,合同的某些部分使用语言手段明确表达,而某些部分则隐含表达(您依赖于人们知道什么是绝对值)。

    语言中的接口用于将合同与实现分离。在小程序中使用它可能有点矫枉过正,但在执行大型程序时会有所回报(这不必涉及团队合作)。

    呃,结果比我预期的要长。希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      接口是 Dart 中类型系统的一部分,类型声明是可选的。这意味着接口也是可选的。

      接口可帮助您记录对象响应的方法。如果你实现了接口,那么你就承诺实现接口中的所有方法。

      Dart 使用此信息来显示类型不匹配的编译时警告,为代码辅助提供更有用的建议并协助进行一些重构。

      【讨论】:

      • 当然,我知道它们是可选的。而且我看不到使用它们的价值。编译时警告似乎不是一个大胜利。但是,我承认我的评估可能从根本上是错误的。这就是为什么我一直在寻找一个真实的例子来展示使用接口的价值。
      猜你喜欢
      • 1970-01-01
      • 2013-10-19
      • 2010-12-13
      • 1970-01-01
      • 2017-04-15
      • 1970-01-01
      • 2013-05-21
      • 2011-02-04
      • 2022-01-02
      相关资源
      最近更新 更多