Pull to refresh

Comments 6

Помню, extension methods есть в Ruby, но их использование сильно порицалось

Пример: разработчик мог дописать к классу строки метод toPasswordHash, спрятать это где-то в глубине проекта, и потом новый сотрудник будет недоумевать, что за "foobar".toPasswordHash

Или, что ещё хуже, переопределить метод capitalize для тех же строк для соответствия некоей бизнес-логике, и тогда будет большим сюрпризом, что "foobar".capitalize ведёт себя по-другому.

Я ради интереса погуглил детали реализации такой фичи в Ruby и понял, что под капотом просто лежат принципиально разные механизмы. Ключевое отличие - разрешение вызовов в runtime (Ruby) против compile-time (Dart).

В Dart extension methods - просто синтаксический сахар, связывающий функцию (статическую) с определением класса. На самом деле под капотом класс никак не изменяется.

Думаю, от этого и идет порицание подобной фичи в Ruby.

Для меня ET это аналог type branding в TypeScript и в этом смысле используется чрезвычайно широко. Это исключительно полезная штука в делегировании поиска ошибок компилятору. Все примитивные идентификаторы в виде строк или чисел я оборачиваю в ЕТ для того, чтобы никогда не спутать между собой при передаче в методы, создании дженериков и вообще везде. Раньше для этого не ленился делать классы-обертки

Но ведь enums для этого неплохо подходят?

А как можно применить enum в случае, когда бекенд (например), возвращает строковые нефиксированные идентификаторы для своих сущностей? Какие-нибудь base58 или UUID? Для сколь нибудь сложного приложения таких типов идентификаторов будут десятки и даже сотни. Если их всех выражать через String, то вероятность спутать один архитектурно несовместимый тип с другим где-то в глубинах приложения (например UserID с LocationID или UserGroupID) стремится к единице. Понятно, что все это можно отловить тестами и все такое (когда-нибудь). Мне предпочтительнее такая организация работы при которой подобные опечатки отлавливаются линтером мгновенно и без моего участия.

Еще пример использования extension types - замена т.н Sentinel в том случае, если нужно обнулять поле класса при copyWith. Есть разные способы, раньше я делал объекты-врапперы, сейчас работаю через extension types

extension type const CWValue<T extends Object>._((T?, Type) _value) implements Object {
  const CWValue(T? value) : this._((value, T));

  static T? resolve<T extends Object>(CWValue<T>? v, T? originalValue) => v == null ? originalValue : v._value.$1;

  T? get value => _value.$1; // ignore: avoid-renaming-representation-getters
}


class MyClass {
    final int? a;

    MyClass({
        required this.a,
    });

    MyClass copyWith({
        CWValue<int>? a,
    }) {
        return MyClass(
           a: CWValue.resolve(a, this.a),
        );
    }
}

final a = MyClass(a: 100)
final b = a.copyWith(a: CWValue(200))
final b = a.copyWith(a: CWValue(null))
Sign up to leave a comment.

Articles