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

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

abstract class BaseType {}

abstract class MixinTypeA implements BaseType {}

abstract class MixinTypeB<T extends MixinTypeA> implements BaseType {
  Future<T> mixinMethod({bool argA = true,
      bool argB = true,
      bool argC = true}) =>
    someMethodCall()
}

abstract class BaseTypeA extends BaseType implements MixinTypeA {
  // declares a constructor
  BaseTypeA();
}

abstract class BaseTypeB extends BaseType implements MixinTypeB {
  // declares a constructor
  BaseTypeB();
}

abstract class TypeA extends BaseTypeA {}

class TypeB extends BaseTypeB with MixinTypeB<TypeA> {}

В этом случае TypeB выдаст ошибку. Это потому, что он пытается смешать MixinTypeB<TypeA>. Поскольку TypeB уже расширяет BaseTypeB, который реализует MixinTypeB с предполагаемым общим <MixinTypeA>, интерфейс MixinTypeB реализуется дважды с двумя разными (хотя и связанными по наследству) интерфейсами: TypeA и MixinTypeA.

По сути, универсальный T существует для того, чтобы мой код оставался СУХИМ. Пример метода в MixinTypeB — это один из различных потенциальных методов, которые может иметь класс с определенной сигнатурой типа T. Я не знаю, как обойти новое ограничение без ущерба для структуры наследования этой системы типов.


person aemino    schedule 12.04.2018    source источник


Ответы (1)


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

abstract class BaseType {}

abstract class MixinTypeA implements BaseType {}

abstract class MixinTypeB<T extends MixinTypeA> implements BaseType {
  Future<T> mixinMethod({bool argA = true,
      bool argB = true,
    bool argC = true}) => null;
}

abstract class BaseTypeA extends BaseType implements MixinTypeA {
  // declares a constructor
  BaseTypeA();
}

abstract class BaseTypeB<T extends MixinTypeA> extends BaseType implements MixinTypeB<T>{
  // declares a constructor
  BaseTypeB();
}

abstract class TypeA extends BaseTypeA {}

class TypeB extends BaseTypeB<TypeA> with MixinTypeB<TypeA> {}

Если вам не нужно смешивать BaseTypeB с любыми другими экземплярами MixinTypeB, то следующий более простой подход также может работать:

abstract class BaseType {}

abstract class MixinTypeA implements BaseType {}

abstract class MixinTypeB<T extends MixinTypeA> implements BaseType {
  Future<T> mixinMethod({bool argA = true,
      bool argB = true,
    bool argC = true}) => null;
}

abstract class BaseTypeA extends BaseType implements MixinTypeA {
  // declares a constructor
  BaseTypeA();
}

abstract class BaseTypeB extends BaseType implements MixinTypeB<TypeA>{
  // declares a constructor
  BaseTypeB();
}

abstract class TypeA extends BaseTypeA {}

class TypeB extends BaseTypeB with MixinTypeB<TypeA> {}
person Leaf Petersen    schedule 12.04.2018
comment
Прежде всего, я хотел бы поблагодарить вас за предложение. Ваше первоначальное предположение верно - мне нужно иметь возможность использовать BaseTypeB для нескольких классов. Однако это решение, многопоточность, кажется мне немного странным. Например, одно из предостережений для этого решения заключается в том, что оно будет странно отображаться в dartdocs, при этом общий вид, который бесполезен для пользователя, будет виден. Логически решение имеет смысл. Но на практике для меня это больше похоже на обходной путь, чем на правильное решение. Однако, насколько я понимаю, это может быть единственным решением. Мне интересно услышать ваши мысли! - person aemino; 13.04.2018
comment
Я не думаю, что у меня действительно достаточно контекста вашего исходного кода, чтобы сказать, является ли это обходным путем или правильным решением. :) Основываясь на этом примере, мне кажется странным, что вы сделали MixinTypeB общим вместо того, чтобы просто заставить mixinMethod возвращать Future<MixinTypeA>, а затем развернуться и использовать MixinTypeB без общего аргумента. Единственная причина, по которой я могу сказать, что BaseTypeB реализует MixinTypeB, заключается в том, что вы можете использовать его как интерфейс, который имеет mixinMethod верно? Но почему вы не заботитесь о точном универсальном типе на этом интерфейсе? - person Leaf Petersen; 14.04.2018
comment
Цель использования дженериков в этом случае в основном состоит в том, чтобы mixinMethod иметь правильную сигнатуру типа. По сути, TypeA и TypeB связаны. Унаследованный mixinMethod для TypeB должен возвращать TypeA. Кроме того, если бы я должен был создать подкласс для TypeA (и, следовательно, также для TypeB), SubtypeB должен был бы иметь свой mixinMethod, возвращающий SubtypeA. Я надеюсь это имеет смысл! :) - person aemino; 14.04.2018
comment
Да, имеет смысл, что MixinTypeB будет общим, что мне не совсем понятно, так это то, почему исходный код сделал BaseTypeB реализующим MixinTypeB<dynamic> вместо того, чтобы либо вообще не реализовывать его (поскольку вы все равно смешиваете его с TypeB), либо реализовать его на конкретном типе. Не говорю, что это неразумно, просто я этого не понял. - person Leaf Petersen; 17.04.2018
comment
В более общем смысле, реализация одного и того же интерфейса с разными универсальными аргументами определенно имеет место, и вполне возможно, что этот код является одним из них. Для Dart 2 мы чувствовали, что эти варианты использования были достаточно редкими, чтобы их стоило обходить, когда они возникают, поскольку есть и другие преимущества отказа от поддержки этого. Существует предложение по поддержке вывода аргументов типа, что может уменьшить шаблонный код в клиентском коде для этого: github.com/dart-lang/sdk/blob/master/docs/language/informal/ . - person Leaf Petersen; 17.04.2018
comment
BaseTypeB реализует MixinTypeB, потому что требует некоторых свойств MixinTypeB (это не ясно из приведенного мной упрощения). Кажется, что на данный момент универсальная многопоточность — мой единственный вариант. Я с нетерпением жду новой системы примесей и надеюсь, что она сможет справиться с такими случаями. Спасибо! - person aemino; 17.04.2018