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

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

Прямо сейчас, в Дарте, каждый класс A определяет неявный интерфейс, который может быть реализован другим классом B с помощью ключевого слова implements. Нет явного способа объявления интерфейсов, например, в Java, где интерфейс содержит только нереализованные методы (и в конечном итоге статические переменные). В Dart, поскольку интерфейсы определены классами, методы интерфейса A могут быть уже реализованы, но класс, реализующий B все же должен переопределять эти реализации.

Мы можем видеть эту ситуацию из следующего фрагмента кода:

class A {
  void m() {
    print("method m");
  }
}

// LINTER ERROR: Missing concrete implementation of A.m
// Try implementing missing method or make B abstract.
class B implements A {
}

В Dart, mixin также определяется с помощью обычных объявлений класса...

... В принципе, каждый класс определяет mixin, который можно извлечь из него. Однако в этом предложении смесь может быть извлечена только из класса, у которого нет объявленных конструкторов. Это ограничение позволяет избежать осложнений, возникающих из-за необходимости передавать параметры конструктора по цепочке наследования.

Mixin - это в основном класс, который может определять как нереализованные, так и реализованные методы. Это способ добавления методов в другой класс без необходимости логического использования наследования. В Dart mixin применяется к суперклассу, который распространяется через "нормальное" наследование, как в следующем примере:

class A {
  void m() {
    print("method m");
  }
}

class MyMixin {
  void f(){
    print("method f");
  }
}

class B extends A with MyMixin {
}

В этом случае следует отметить, что B не нужно применять какие-либо дополнительные методы как A и MyMixin.

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

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

Итак, в целом, концепция реализации интерфейса больше связана с установлением контракта с классом, который реализует интерфейс, а концепция mixins (как следует из названия) - это больше о повторном использовании кода (без повторения иерархии наследования).

Когда использовать mixins и когда использовать интерфейсы в Dart? Существуют ли какие-либо эмпирические правила, по крайней мере, для специальных повторяющихся шаблонов при разработке программного обеспечения, где было бы лучше определить mixin и применить его к суперклассу, вместо того чтобы сделать наш класс реализованным интерфейсом? Я был бы признателен за конкретные примеры проектных решений в контексте, где могут использоваться оба интерфейса и mixins, но один используется по другому (по какой-то причине).

Ответ 1

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

Возьмите класс, который реализован как class MyList<T> extends Something with ListMixin<T>... Вы можете использовать этот класс как MyList<int> l = new MyList<int>(); или List<int> l = new MyList<int>(), но вы никогда не должны писать ListMixin<int> l = new MyList<int>(). Вы можете, но не должны, потому что это относится к ListMixin как к типу, и это действительно не предназначено. По этой же причине вы всегда должны писать Map m = new HashMap(); а не HashMap m = new HashMap(); - тип Map, это деталь реализации, которая является HashMap.

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

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

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

Итак, если вы хотите реализовать список, вы можете либо реализовать класс List либо выполнить всю реализацию самостоятельно, либо ListMixin класс ListMixin для повторного использования некоторых базовых функций. Вы все равно можете писать ListMixin implements List<T>, но вы получите это путем наследования ListMixin.

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

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

Пример:

class Counter {
  int _counter = 0;
  int next() => ++_counter;
}
class Operation {
  void operate(int step) { doSomething(); }
}
class AutoStepOperation extends Operation with Counter {
  void operate([int step]) {
    super.operate(step ?? super.next());
  }
}

Что действительно происходит, так это то, что вы создаете новый класс "Operation with Counter". Это эквивалентно:

Пример:

class Counter {
  int _counter = 0;
  int next() => ++_counter;
}
class Operation {
  void operate(int step) { doSomething(); }
}
class $OperationWithCounter = Operation with Counter;
class AutoStepOperation extends $OperationWithCounter {
  void operate([int step]) {
    super.operate(step ?? super.next());
  }
}

Приложение mixin Counter to Operation создает новый класс, и этот класс появляется в цепочке суперклассов AutoStepOperation.

Если class X extends Y with I1, I2, I3 тогда вы создаете четыре класса. Если вы просто выполняете class X extends Y implements I1, I2, I3 тогда вы создаете только один класс. Даже если все I1, I2 и I3 являются полностью пустыми абстрактными интерфейсами, использование with их применением эквивалентно:

class $X1 extends X implements I1 {}
class $X2 extends $X1 implements I2 {}
class $X3 extends $X2 implements I3 {}
class X extends $X3 {}

Вы бы не написать, что непосредственно, так что вы должны написать его, используя with либо

Ответ 2

Языки, такие как Java и С#, используют интерфейсы для множественного наследования типа вместо множественного наследования реализации. Есть сложности компромиссов, что языки с множественным наследованием реализации (например, Eiffel, C++ или Dart) должны иметь дело с тем, что разработчики Java и С# решили избежать.

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

Пример:

abstract class IntA {
  void alpha();
}

abstract class IntB {
  void beta();
}

class C extends IntA with IntB {
  void alpha() => print("alpha");
  void beta() => print("beta");
}

void main() {
  var c = new C();
  IntA a = c;
  IntB b = c;
  a.alpha();
  b.beta();
}

Dart имеет множественное наследование реализации (через mixins), поэтому ему также не нужно наследование нескольких интерфейсов как отдельная концепция, а также способ отдельно определять интерфейсы как автономные объекты. Неявные интерфейсы (через implements п) используются, чтобы иметь возможность документировать или проверить, что один класс реализует по крайней мере, один и тот же интерфейс, как другой. Например, Int8List реализует List<int>, хотя базовая реализация полностью отличается.

Использование наследования/миксинов и неявных интерфейсов, полученных с помощью implements, обычно ортогонально; вы, скорее всего, будете использовать их в совокупности, а не вместо друг друга. Например, вы можете использовать implements Set<int> для описания желаемого интерфейса реализации битового набора, а затем использовать extends и/или with предложениями, чтобы задействовать фактическую реализацию для этого интерфейса. Причина в том, что ваш битовый Set<int> не будет передавать какую-либо реальную реализацию с помощью Set<int>, но вы все равно хотите использовать их взаимозаменяемо.

Библиотека коллекции предоставляет SetMixin mixin для нас, что требует от нас самих реализовать некоторые основные процедуры и предоставить остальную часть реализации Set<T> на основе этих.

import "dart:collection";

class BitSetImpl {
  void add(int e) { ...; }
  void remove(int e) { ...; }
  bool contains(int e) { ...; }
  int lookup(int e) { ...; }
  Iterator<int> get iterator { ...; }
  int get length { ...; }
}

class BitSet extends BitSetImpl with SetMixin<int> implements Set<int> {
  BitSet() { ...; }
  Set<int> toSet() { return this; }
}

Ответ 3

Я получаю эту ошибку, может кто-нибудь сказать мне, где я иду не так

импортировать "пакет: scoped_model/scoped_model.dart";

import './connected_products.dart';

Класс MainModel расширяет модель с помощью ConnectedProductsModel, UserModel, ProductsModel {}

ошибка:

Ответ 4

Интерфейсы dart, как и другой язык, определяют контракт для реализации любого класса, этот контракт требует реализации его открытых свойств и методов.

Смешать это просто еще один способ добавить функциональность в ваш класс, потому что в дартс не существует мульти расширяет.

Ответ 5

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

  1. В ООП интерфейс - это то, что заставляет производный класс реализовывать список открытых полей и методов.

Но в отличие от других традиционных языков программирования, таких как С# и JAVA, Dart не имеет явных типов интерфейса. Каждый класс по умолчанию определяет свой собственный интерфейс, состоящий из открытых полей и методов. Итак, каждый класс может выступать в качестве интерфейса в Dart.

Ключевое слово Implements - реализовать интерфейс. Кроме того, класс может реализовывать несколько интерфейсов.

  1. В ООП наследование подразумевает разделение поведения между классами. Мы не можем делиться функциями с интерфейсом. Поэтому, когда мы реализуем класс, мы не можем делиться его поведением.

Если вы хотите поделиться поведением между этими двумя классами, вам следует использовать ключевое слово extends.

  1. В ООП mixin - это класс, который содержит методы для использования другими классами. В отличие от интерфейса и подхода наследования, mixin не обязательно должен быть родительским классом этих других классов.

Таким образом, миксин не налагает ограничения на использование и не заставляет ограничения типа.

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

Взято отсюда