Первоначально я писал пост в журнале изменений Dart 3 здесь:

и один из разделов должен был быть посвящен новым ограничениям на реализацию/расширение (implementation/extension). И я написал его... однако, он получился очень длинным, и я не думаю, что справился с этим разделом. Поэтому решил, что вместо того, чтобы поместить его в один раздел другого поста, я помещу его здесь.

Модификаторы

Если люди и жалуются на Dart, так это на отсутствие модификаторов. Как это работает во Flutter: вы можете иметь обычное имя переменной, например foo, и если вы поставите перед ним знак подчеркивания, например _foo, она станет приватной.

Мне это очень нравится, потому что это значительно упрощает код. Больше никаких public и private повсюду. Кроме того, использование подчеркивания для обозначения переменной как private уже является обычной практикой в таких языках, как Python.

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

Но это материал для другого поста. Теперь я хотел бы поговорить о распространенной жалобе на эту систему. Некоторые люди считают, что эта система подчеркивания является слишком ограничительной. Это запрещает некоторые более интересные модификаторы, такие как protected и package private. Очевидно, что я не согласен с тем, что это проблема. Но команда Dart (вроде как) прислушалась к вашим мольбам.

Вы по-прежнему не получаете дополнительных модификаторов переменных. Но вы получаете лучшее, что есть: модификаторы классов.

Ранее в Dart

Раньше у нас был только один модификатор класса: abstract. Это работает так же, как и в любом другом языке: вы должны расширить или реализовать abstract class, при этом вы не можете создать его экземпляр. Также, он может играть роль интерфейса (interface). Мне нравится такая простота. Но некоторые этого не делают. Итак, в Dart 3 мы получаем не один, не два, не три, а пять новых модификаторов классов: Base, Interface, Final, Sealed и Mixin. Да, теперь вам нужно явно пометить класс как Mixin, чтобы использовать его в качестве миксина, тогда как раньше этого не требовалось.

Если вы не знаете, что такое mixin, то это ответ Dart на множественное наследование. Многие языки (включая Dart) по какой-то причине решили, что нельзя стрелять себе в ногу, и отключили множественное наследование. Но что, если вам нужно наследоваться от нескольких вещей? Тогда используйте mixin, что позволит вам наследоваться от нескольких вещей; но дети не должны наследоваться от миксина (прим.: Then use a mixin, this allows you to inherit from multiple things but children do not have to inherit from the mixin). Как это поможет? Я не знаю, просто соглашайтесь с этим.

Всё это описано здесь:

Итак, вот что делают новые классы:

  • Base: допускает расширение, но не реализацию.

  • Interface: позволяет реализовать, но не расширять (аналогично другим языкам).

  • Final: запрещает как расширение, так и внедрение

  • Sealed: абстрактный и окончательный

  • Mixin: могут быть смешаны

прим.: на английском это легче воспринимать
  • Base: allows extension but not implementation.

  • Interface: allows implementation but not extension (similar to other languages).

  • Final: disallows both extension and implementation

  • Sealed: abstract and final

  • Mixin: can be mixined

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

Уже запутались? Нет? Хорошо, потому что вы также можете комбинировать эти модификаторы. Но это не всё. Вот шпаргалка.

Таблица комбинаций модификаторов в Dart 3.0.0
Таблица комбинаций модификаторов в Dart 3.0.0

Зачем?

Так что, если вы похожи на меня, то задаетёсь вопросом, в чем смысл всех этих новых вещей. Это объясняется ранее в документе. В нём указаны 4 причины для этого:

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

  • Можно изменить поведение класса, переопределив что-то критическое (в документе приводится пример геттера canWithdraw).

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

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

Заключение

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

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

Потому что некоторые из этих проблем чрезвычайно нишевые. Например, я даже не знал, что номер 3 - это проблема. И похоже, что должен быть способ сделать это ошибкой компилятора.

Но я не дизайнер языков. Я имею в виду, что однажды я создал свой собственный язык JVM. Это было круто, но в основном это был язык шуток. Возможно, команда Dart действительно рассматривала какие-то другие решения, но решила отказаться от них.

Ну, скорее всего, я не буду использовать ни одно из этих новых ключевых слов. И я не очень часто или вообще не занимаюсь наследованием. Если я и делаю это, то в основном только для тестирования. Так что это не сильно повлияет на меня.

Комментарии (1)