Написание абстрактных классов при использовании Freezed

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

Я понимаю, что предлагает документация, но мне нужно больше.

Я создал репозиторий , чтобы показать, что мне нужно, на случай, если вы захотите перейти к делу. Все, что мне нужно, это эти тесты, чтобы пройти и работать как задумано.

Мне нужен чистый, читаемый и удобный способ сделать это.

Сценарий

В репозитории вы найдете небольшой репродукционный футляр:

  • Base абстрактный класс;
  • Класс A, который расширяет класс Base (я хотел все упростить, но в моем реальном случае использования есть больше подклассов, скажем X, Y, Z и т. д.);
  • A необходимо реализовать с помощью Freezed, с сериализацией/десериализацией JSON;

Резюме: Base существует, чтобы написать точки соприкосновения между A, X, Y и Z; как упоминалось выше, Freezed является обязательным для этих подклассов.

Цель

Моя цель — использовать композицию.

Пусть класс B имеет Base get myValue геттер с Freezed. Неудивительно, что я хочу взаимодействовать с этим значением, обращаясь к его методу copyWith (или другим, например toJson), но это довольно быстро усложняется (см. проблему 1).

Снова прочитайте тесты на желаемый результат.

Проблема 1

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

Например, следующее:

abstract class Base {
  const Base();

  Function copyWith();
  Map<String, dynamic> toJson();

  String get id;
}

не будет работать, потому что подклассы будут чувствовать двусмысленность в отношении того, какой метод суперкласса использовать (ошибка здесь): должен ли он использовать тот, который сгенерирован @freezed, или абстрактный? Это ошибка времени компиляции.

Я понятия не имею, как правильно написать контракт, используя Freezed.

Проблема 2

Сначала build_runner справедливо жалуется, что не может сгенерировать метод fromJson в B, потому что у Base нет метода @JsonSerializable.

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

Такое зверство находится в этом файле.

Как можно достичь этого чистым способом?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
160
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваша проблема, вероятно, связана с Base определением пустого copyWith

Freezed реализует copyWith не как метод, а как геттер, который возвращает функцию (или вызываемый объект). Это необходимо для поддержки copyWith(foo: null), но несовместимо с вашим интерфейсом.

Расширение Base также может быть проблемой. Freezed не очень хорошо поддерживает наследование из-за языковых ограничений. Вам, вероятно, лучше реализовать Base или сделать его миксином.

В общем, вы могли бы сделать:

mixin Base {
  int get id;

  Map<String, dynamic> toJson();
}

@freezed
class A with _$A, Base {
  const A._();

  factory A({
    required int id;
  }) = _A;
  
  factory A.fromJson(Map<String, dynamic> json) => _$AFromJson(json);

  @override
  Map<String, dynamic> toJson() => _$AToJson(this);
}

Спасибо, Реми!! В конце концов, все, что мне нужно, — это способ доступа к сгенерированным Freezed методам из общего интерфейса, а также несколько общих методов получения, которые необходимо реализовать в каждом подклассе. Я попробую этот Mixin и посмотрю, смогу ли я сойти с рук.

venir 27.10.2022 11:21

Нет, к сожалению, это не работает. Пожалуйста, проверьте мой репозиторий еще раз (я только что выдвинул эти предложения) и убедитесь, что: (1) мне все еще нужно реализовать BaseConverter, что является еще одним запахом, иначе B не будет генерировать свой код; и (2) что-то ломается при генерации класса A, так как a.freezed.dart содержит эту ошибку: The method 'copyWith' isn't defined for the type '<unknown>'. Может ты хотел написать class A with _$A, Base ?

venir 27.10.2022 11:41

Ну, Base не имеет fromJson, так что вы не можете волшебным образом его расшифровать. Если вам не нужен JsonConverter, не стесняйтесь добавлять fromJson к Base

Rémi Rousselet 27.10.2022 11:49

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

venir 27.10.2022 12:03

Привет еще раз и еще раз спасибо за помощь! Я понимаю, что у Base нет fromJson, моей целью было вызвать его подклассы, но... это невозможно! Это ошибка в моих рассуждениях. B.fromJson не может знать, должен ли он запускать A.fromJson или любой другой X.fromJson этот подкласс Base. Так что пользовательский BaseConverter должен быть реализован, от этого никуда не деться. Еще немного поразмыслив, я пришел к выводу, что эта проблема просто не решается с помощью Freezed и что я должен реализовать все вручную. Я помогу себе с Freezed в других контекстах.

venir 27.10.2022 12:37

В частности, (1) я не могу решить проблему абстрактного метода copyWith и (2) решение вышеупомянутой проблемы путем введения свойства key портит решение, которое вы нашли (toJson на самом деле не настраивается, потому что конвертер будет использовать этот метод на тип _$_A, см. репозиторий). Если бы в Dart были классы данных, я бы просто создал Base абстрактный класс данных, и все. Грустный.

venir 27.10.2022 12:44

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

venir 27.10.2022 12:48
Ответ принят как подходящий

После двух дней работы и исследований мне удалось выбраться из этой неразберихи.

  1. Мне пришлось пока отказаться от freezed (я просто оставлю его для системы союзных типов);
  2. О реализации этого вручную, очевидно, не может быть и речи;
  3. Я использовал этот пакет: dart_mappable, он делает именно то, о чем я просил выше.

Я опубликую здесь быструю реализацию псевдокода, достижимую с помощью dart_mappable:

@MappableClass()
abstract class MyBase with MyBaseMappable {
  const MyBase(this.id);
  final String id;
}

@MappableClass()
class A extends MyBase with AMappable {
  const A(super.id);
}

@MappableClass()
class B with BMappable {
  const B(this.value);
  final MyBase value;
}


void main() {
  const a = A('hello');
  const b = B(a);
  print(b.value.copyWith(id: "lol"));  // Yes!
}

Для работы требуется версия 2 версии dart_mappable, которая в настоящее время находится в состоянии предварительного выпуска.

Другие вопросы по теме