Введение в Dart для Java-программистов

Введение в Dart для Java-программистов

1. Вступление

Dart это язык программирования, используемый во Flutter, мобильном SDK от Google. Сегодня вы освоите язык Dart, при этом особый упор будет сделан на вещах, неочевидных для Java-разработчика.

Вы сможете писать функции в Dart уже через 1 минуту, скрипты через 5 минут, а приложения через 10 минут!

Что вы узнаете

  • Как создавать конструкторы
  • Способы передачи параметров
  • Когда и как создавать геттеры (getters) и сеттеры (setters)
  • Приватность (privacy) в Dart
  • Как создавать фабрики (factories)
  • Функциональное программирование в Dart
  • Другие основные концепции Dart

Что вам понадобится

Для этого урока вам нужен только браузер!

Все примеры вы будете писать и запускать на выполнение в DartPad, интерактивной веб-утилите, позволяющей использовать всю функциональность языка Dart и основных его библиотек. Но вы можете использовать IDE, например, WebStorm, IntelliJ с плагином Dart, или Visual Studio Code с расширением Dart Code.

2. Создаём класс

Начнём с создания простого класса Dart с функционалом, подобным Bicycle class из учебника Java. Класс Bicycle содержит несколько приватных переменных с геттерами и сеттерами. Метод main() создаёт экземпляр Bicycle и выводит его в консоль.

Введение в Dart для Java-программистов

Открываем DartPad

В этом уроке для каждого набора упражнений создаётся новый экземпляр DartPad. Ссылка ниже откроет экземпляр с примером по-умолчанию. Вы можете использовать одну и ту же вкладку с DartPad на весь урок, но помните, что при нажатии кнопки Reset DartPad вернёт всё к началу, затерев ваш код.

Открыть DartPad

Класс Bicycle

Создадим класс Bicycle (выше метода main()), содержащий три переменных. Удалите из main() всё содержимое, как в этом примере:

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

Наблюдения

  • Главный метод Dart называется main() или (если вам нужен доступ к аргументам командной строки) main(List<String> args).
  • Метод main() находится на самом верхнем уровне. В Dart вы можете размещать код вне классов. Переменные, функции, геттеры и сеттеры — все они могут быть вне классов.
  • В оригинальном примере из Java были приватные переменные, 
    заданные тегом private , которого нет в Dart. О приватности будет написано детально в разделе «Добавим переменную только для чтения».
  • Ни main(), ни Bicycle не указаны как public, потому что все идентификаторы публичны по-умолчанию. В Dart просто нет ключей public, private или protected.
  • Dart по стандарту использует отступ в 2 символа, вместо 4. Но вам не следует об этом беспокоится благодаря утилите dartfmt. Как сказано в соглашении (Effective Dart), «Официальное правило по количеству пробелов в Dart — такое, какое делает dartfmt

Конструктор Bicycle

Добавьте следующий конструктор в класс Bicycle:

Bicycle(this.cadence, this.speed, this.gear);

Наблюдения

  • У этого конструктора нет тела, это допустимо в Dart.
  • Если вы забудете поставить точку с запятой (;) в конце такого конструктора, DartPad выведет ошибку: «A function body must be provided.» (тело функции отсутствует).
  • Слово this в параметрах конструктора — это удобный способ присвоения значений переменным экземпляра.
  • Код выше полностью эквивалентен следующему:
Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

Форматируем код

Отформатировать код Dart можно нажатием на Format сверху в DartPad. Форматирование бывает полезно при вставке стороннего кода.

Нажмите Format.

Инициализируем и выведем на печать экземпляр bicycle

Добавьте следующий код в функцию main():

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

Удалите необязательный ключ new :

var bike = Bicycle(2, 0, 1);

Наблюдения

  • Ключевое слово new стало опциональным в Dart 2.
  • Если вы уверены в том, что значение переменной не изменится, можете использовать final вместо var.

Запускаем пример

Нажмите кнопку Run сверху окна DartPad для запуска на выполнение. Если Run недоступна, читайте раздел Проблемы ниже.

Вы должны увидеть следующий вывод:

Instance of 'Bicycle'

Наблюдения

  • Если нет ошибок, значит анализатор типов правильно определил, что var bike = ... это экземпляр Bicycle.

Улучшаем вывод

Вывод «Instance of ‘Bicycle'» корректен, но малоинформативен. Все классы Dart имеют метод toString(), который вы можете переопределить для получения лучшего вывода.

Добавьте следующий метод toString() в любое место класса Bicycle:

@override
String toString() => 'Bicycle: $speed mph';

Наблюдения

  • Аннотация @override сообщает анализатору, что вы хотите переопределить какой-то метод. Анализатор проверит, что вы корректно реализовали переопределение.
  • Dart поддерживает одиночные или двойные кавычки для определения строк.
  • Используйте строковую интерполяцию для указания значения выражения внутри строкового литерала: ${expression}. Если выражение это идентификатор, вы можете обойтись без скобок: $variableName.
  • Однострочные методы используют стрелочную (=>) нотацию.

Запускаем пример

Нажмите Run.

Теперь вы должны увидеть следующее:

Bicycle: 0 mph

Проблемы?
Проверьте свой код

Добавим переменную только для чтения

В оригинальном примере на Java переменная speed — только для чтения — она приватная и у неё есть только метод геттер. Сделаем то же самое на Dart.

Откройте bicycle.dart в DartPad (или продолжайте с вашим кодом)

Чтобы сделать идентификатор в Dart приватным, напишите его имя  начинающимся с символа подчёркивания (_). Преобразуем speed в переменную «только для чтения», изменив её имя, затем добавим геттер.

В конструкторе Bicycle удалите параметр speed:

Bicycle(this.cadence, this.gear);

В main() удалите второй параметр (speed) из вызова конструктора:

var bike = Bicycle(2, 1);

Замените все speed на _speed. (в 2 местах)

Подсказка: Если вы используете JetBrains IDE, вы легко сможете переименовать все вхождения переменной, кликнув правой кнопкой на её имени, затем выбрать Refactor > Rename… из появившегося меню.

Инициализируем _speed равной 0:

int _speed = 0;

Добавим следующий геттер в класс Bicycle:

int get speed => _speed;

Наблюдения

  • Неинициализированные переменные (и даже числа) имеют значение null.
  • Компилятор Dart делает приватными все переменные, начинающиеся с подчёркивания.
  • По-умолчанию, Dart предлагает неявные геттеры и сеттеры для всех публичных экземпляров переменных. Вам не потребуется создавать свои геттеры/сеттеры, пока не будет необходимости сделать это для переменных «только для чтения» или «только для записи», рассчитать или сверить значение переменной и т.п.
  • Начните с простого поля, типа bike.cadence, затем отрефакторите его с применением геттеров и сеттеров. API при этом останется прежним. Другими словами, путь от поля до геттера/сеттера очень прост в Dart.

Добавьте следующие методы в класс Bicycle:

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

Окончательный пример на Dart выглядит схоже с оригинальным на Java, но более компактный — всего 23 строки вместо 40:

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

Проблемы?
Проверьте свой код

3. Используем опциональные параметры (вместо перегрузки)

Следующий урок посвящён классу Rectangle.

Код Java часто содержит перегружаемые конструкторы, такие, в которых конструкторы имеют одинаковое имя, но отличаются количеством или типом параметров. Dart не поддерживает такие конструкторы, но предлагает разные подходы к этой задаче.

Откройте пример Rectangle в DartPad

Добавляем конструктор Rectangle

Мы добавим одиночный пустой конструктор, полностью заменяющий все четыре конструктора в примере на Java:

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

В этом конструкторе используются опциональные именные параметры.

Наблюдения

  • this.origin, this.width и this.height используются для присвоения значений переменным экземпляра внутри конструктора.
  • this.origin, this.width и this.height — опциональные именные параметры. Именные параметры заключены в фигурные скобки ({}).
  • Синтакс this.origin = const Point(0, 0) указывает значение по-умолчанию, равное Point(0,0) для переменной origin. Это значение должно быть константой времени компиляции (compile-time constant). В этом конструкторе значения по-умолчанию указаны для всех трёх переменных.

Улучшаем вывод

Добавим следующую функцию toString() в класс Rectangle:

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

Используем конструктор

Чтобы проверить создание класса только с нужными нам параметрами, заменим main() следующим кодом:

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

Наблюдения

  • Конструктор класса Rectangle на Dart это всего одна строка, в отличие от 16 строк кода в примере на Java.

Запускаем пример

Вы должны увидеть следующее:

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

Проблемы?
Проверьте свой код

4. Создаём фабрику

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

Рассмотрим два способа реализации фабрики:

  • 1: Создадим функцию высшего порядка
  • 2: Создадим конструктор фабрики

Для примера используем класс Shape, создающий фигуры и выводящий информацию об их площадки:

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

Открыть пример в DartPad

В консоли вы должны увидеть рассчитанную площадь круга и квадрата:

12.566370614359172
4

Наблюдения

  • Dart поддерживает абстрактные классы.
  • В одном файле можно создать множество кллассов.
  • dart.math — одна из основных библиотек в Dart. Также там есть dart:core, dart:async, dart:convert и dart:collection.
  • В Dart 1.x константы основной библиотеки были в верхнем регистре (PI); в Dart 2 они в нижнем регистре (pi).
  • В этом коде два геттера, рассчитывающих значения:
    num get area => pi * pow(radius, 2); // Круг
    num get area => pow(side, 2); // Квадрат

Создаём функцию высшего порядка

Создадим фабрику как функцию высшего порядка, добавив следующую функцию на верхний уровень (вне классов):

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

Вызовем фабричную функцию, заменив первые две строки метода main():

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

Запускаем пример

Вывод должен быть таким же, как раньше.

Наблюдения

  • Если функцию вызвать с параметрами, отличающимися от 'circle' или 'square', она выбросит исключение.
  • Dart SDK имеет классы для большинства типовых исключений, также вы можете расширить класс Exception для создания своих исключений.
  • Когда встречается исключение, DartPad выводит Uncaught. Для более детальной информации об ошибке, используйте блоки try-catch с выводом исключения. Также дополнительно можно посмотреть в этом примере DartPad.
  • Для использования одиночных кавычек внутри строки, экранируйте их обратной косой чертой ('Can\'t create $type.') или создавайте строку в двойных кавычках  ("Can't create $type.").

Проблемы?
Проверьте свой код

Фабричный конструктор

Ключевое слово factory создаёт фабричный конструктор в Dart.

Добавим его в абстрактный класс Shape:

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

Замените первые две строки в main() на:

  final circle = Shape('circle');
  final square = Shape('square');

Удалите функцию shapeFactory(), добавленную ранее.

Наблюдения

  • Код в фабричном конструкторе идентичен коду в функции shapeFactory().

Проблемы?
Проверьте свой код

5. Реализуем интерфейс

В языке Dart не нужно ключевое слово interface потому что каждый класс создаёт интерфейс.

Откройте пример Shapes в DartPad (или продолжайте в своей копии)

Добавим класс CircleMock, расширяющий класс Circle:

class CircleMock implements Circle {}

Вы должны увидеть ошибку «Missing concrete implementations» (отсутствует реализация). Уберём эту ошибку, создав переменные area и radius:

class CircleMock implements Circle {
  num area;
  num radius;
}

Наблюдения

  • Несмотря на то, что класс CircleMock не описывает никакого поведения, он корректен в Dart — анализатор не выводит ошибки.

Проблемы?
Проверьте свой код

6. Функциональное программирование в Dart

В функциональном программировании вы можете делать такие вещи как:

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

Dart поддерживает всё это. В Dart даже функции это объекты (имеющие тип Function) и это значит, что функции могут быть присвоены переменным или переданы как аргументы, другим функциям. Также вы можете вызвать класс Dart как функцию, как в этом примере.

Следующий пример описан в императивном (не функциональном) стиле:

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

Откройте пример в DartPad

Его вывод должен быть таким:

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

Наблюдения

  • При использовании интерполяции строк, строка ${'a' * length} читается как «символ 'a' повторить length раз.»

Конвертируем императивный код в функциональный

Удалите императивный цикл for() {...} в main() и замените его таким однострочным выражением:

  values.map(scream).forEach(print);

Запустите пример

Функциональный подход также выведет эти же строки.

Проблемы?
Проверьте ваш код

Больше итераторов

Списки и итераторы из dart:collection поддерживают методы fold, where, join, skip и другие. В Dart также есть Map-ы и Set-ы.

Замените строку values.map() в main() следующей:

  values.skip(1).take(3).map(scream).forEach(print);

Запустите пример

Вывод должен быть примерно такой:

Aaah!
Aaaah!
Aaaaaah!

Наблюдения

  • skip(1)пропускает первое значение, 1, в списке values .
  • take(3)берёт следующие 3 значения — 2, 3 и 5 — в списке values.
  • Остальные значения списка проигнорированы.

Проблемы?
Проверьте свой код

7. Поздравляем!

По завершении этого урока вы должны были узнать большинство отличий между Java и Dart. Dart лёгкий в освоении, у него очень хорошие основные библиотеки и огромная база пакетов. Dart хорошо масштабируется. Сотни инженеров Google используют Dart для создания бизнес-критичных приложений компании.

Дальнейшие шаги

За 20 минут невозможно показать все отличия между Java и Dart. К примеру, мы не рассмотрели следующие темы:

  • async/await, что позволит писать асинхронный код так, как синхронный. Откройте пример в DartPad с анимацией расчёта первых пяти чисел π.
  • Каскадный метод, где всё — builder!
  • Null-безопасные операторы

Источник

Подписывайтесь на новости Flutter! https://t.me/flutterdaily

Звёзд: 1Звёзд: 2 (5 оценок, среднее: 1,60 из 5)
Загрузка...

Leave a Comment