Когда использовать интерфейсы в Dart?

Я читал документацию Dart, и я был немного смущен, возможно, потому, что я прихожу от Ruby, как использовать интерфейсы. Конечно, интерфейсы не уникальны для Dart, и есть довольно много объяснений, когда нужно использовать интерфейс. этот, например, кажется, говорит, что интерфейсы полезны только тогда, когда вы находитесь в команде. Что это значит в мире с открытым исходным кодом, где каждый читает и использует кто-то код еще?

одно интересное объяснение, которое я видел, казалось, подразумевало, что используются интерфейсы:

  1. в языках, которые не имеют множественного наследования, и
  2. если на то пошло, они каким-то образом служат обходным путем для отсутствия множественного наследования.

Я этого не понимаю. Я понимаю, что модули в Ruby являются обходным путем, потому что они позволяют мне определять реальные методы с фактическими телами. Интерфейсы только позволяют мне определите, какие методы должен иметь класс, реализующий его. В чем подвох? Может ли кто-нибудь рассказать о реальном полезном примере, где я могу сразу увидеть значение использования интерфейсов?

P. S. На соответствующую записку, есть ли способ, чтобы использовать множественное наследование в Дарт?

3 ответов


интерфейсы полезны, потому что они позволяют переключать реализации класса, в то же время позволяя проверять, что передаваемый тип соответствует требованиям интерфейса.

Возьмите следующий (часто используемый) пример:

interface Quackable {
  void quack();
}

это определяет требования класса, который будет передан такому методу, как:

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

что позволяет вам использовать любой реализации объекта Quackable, такие as:

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 вы обнаружите, что большинство "классов" на самом деле являются интерфейсами. Список, строка etc... все интерфейсы с реализации по умолчанию. Когда вы звоните

List myList = new List();

вы фактически используете интерфейс списка, и новое ключевое слово перенаправляет из интерфейса в базовую реализацию списка по умолчанию.

что касается развития в команде

интерфейсы полезны в командной разработке, даже в мире с открытым исходным кодом. Интерфейс определяет методы и свойства, которые вы должны создавать, чтобы ваш компонент работал с моим компонентом. Вы можете создать свою собственную тестовую реализацию этого интерфейса, и я могу построить свою конкретную реализацию этот интерфейс, и когда мы оба закончим, мы сможем интегрироваться. Без опубликованного общего интерфейса мне нужно было бы предоставить мою конкретную реализацию, прежде чем вы действительно сможете начать.

надеюсь, что это поможет!


в основном, интерфейсы не имеют ничего общего с множественным наследованием. Можно подделать несколько интерфейсов наследования и злоупотребления при этом, но если вы хотите истинное множественное наследование (или миксины или черты), то Dart не предоставляет их (в настоящее время-я считаю, что миксины найдут свой путь в один прекрасный день).

для чего хороши интерфейсы, это явный контракт. Сказать, что у вас есть два компонента A и B что нужно работать с каждым другой. Вы, конечно, можете позвонить B С A непосредственно, и это будет работать, но в следующий раз вы захотите изменить B, вам придется посмотреть A как он его использует. Это потому что B не выставлял явный интерфейс. Да, правильное слово для интерфейсов не реализовать но разоблачение.

если вы скрываете реализацию B за интерфейсом и только предоставить этот интерфейс для A, тогда вы может измениться B по желанию и только беспокоиться о все еще подвергая тот же интерфейс. Интерфейс может быть даже предоставлен более чем одним классом, и вызывающему абоненту не нужно заботиться (или даже знать).

обратите внимание, что слово интерфейс имеет здесь два значения: общие контракт компонента, который также может быть описан на простом английском языке в документации, и специальная языковая конструкция это поможет вам описание (а также обеспечение) некоторых частей контракта прямо внутри языка программирования.

вам не обязательно использовать языковую конструкцию, но считается хорошим стилем использовать ее для описания тех частей контракта, которые позволяет язык программирования.

теперь, что за черт контракт здесь? Проще говоря, контракт-это описание что компонент ожидает от своего пользователя и что пользователь может ожидать от компонент.

например, скажем, у меня есть метод, который вычисляет абсолютное значение числа:

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 ...
  }
}

обратите внимание, что некоторые части договора просто не могу выражаться на языке программирования -- здесь язык понятия не имеет, что абсолютное значение это, это должно остаться в комментарии. Кроме того, вы не можете выразить, что возвращаемое значение никогда не отрицательно, поэтому это тоже должно оставаться в комментарии. Но на самом деле, читатели вашего кода знаю что такое абсолютное значение (и что оно никогда не является отрицательным), и имя метода довольно ясно о цели, поэтому комментарий можно полностью оставить out:

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

Итак, некоторые части контракта выражены явно, используя языковые средства, а некоторые части выражены неявно (вы полагаетесь на людей, знающих, что такое абсолютная стоимость).

и интерфейсы на языке используются для отделить контракт от реализации. Это, вероятно, излишне использовать в небольших программах, но это окупается при выполнении больших программ (которые не должны включать командную работу).

Uff, это оказалось дольше, чем я ожидал. Надеюсь, это поможет.


интерфейсы являются частью системы типов в Dart, а объявления типов необязательны. Это означает, что интерфейсы также являются необязательными.

интерфейсы помогают документировать методы, на которые отвечает ваш объект. Если вы реализуете интерфейс, то вы обещаете реализовать все методы в интерфейсе.

Dart использует эту информацию для представления предупреждений времени компиляции несоответствий типов, чтобы предоставить более полезные предложения для помощи кода и помочь с некоторыми рефакторинги.