Введение в DART — основные концепции. Часть 1.

Важные концепции Dart

Прежде чем мы перейдем к нашему первому приложению, я хочу дать вам краткий обзор самого Dart, чтобы у вас было больше информации о языке.

Итак, давайте взглянем на диаграмму, которая обобщит несколько важных концепций Dart.  Итак, на этой диаграмме мы рассмотрим четыре различные важные функции Dart.

Во-первых, Dart — это объектно-ориентированный язык программирования с объектно-ориентированным языком. Мы тратим много времени на размышления о том, как моделировать логику в нашем приложении.  Dart является объектно-ориентированным языком, и каждое значение, которым мы манипулируем в программе на Dart, является объектом. Объект представляет экземпляр некоторого класса, а класс является шаблоном или описанием объекта. Если вы никогда раньше не работали с объектно-ориентированным языком программирования или не знаете, что означает термин «класс», это совершенно нормально.

В рамках этого курса мы подробно рассмотрим объектно-ориентированное программирование.

Во вторых, Dart — это статически типизированный язык, похожий на C++, C# или Java, а вот JavaScript или Ruby — примеры динамически типизированных языков. Мы, конечно, будем обсуждать статическую типизацию довольно много в этом курсе, но сейчас просто представьте, что в Dart мы должны убедиться, что любая заданная переменная может содержать данные только одного типа, например, скажем, целое число или строку.

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

Третий Dart имеет синтаксис в стиле C. Так что в целом это будет выглядеть очень похоже на C, C# или JavaScript или даже Java. Вероятно, вы не увидите много странных символов внутри своего кода. Большая часть кода, который мы с вами напишем, будет выглядеть вполне разборчиво.

Наконец, Dart имеет несколько сред выполнения.

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

При запуске из командной строки как отдельной программы Dart выполняется в так называемой Dart VM или виртуальной машине Dart.

Наконец, на мобильном устройстве Dart сначала компилируется в машинный код, поэтому ваше мобильное устройство никогда не коснется фрагмента чистого Dart. Он работает напрямую с машинным кодом.

Это очень общее представление о  различных аспектах Dart.


DartPad — браузерный простейший редактор кода

Мы говорили о нескольких важных замечаниях по языку программирования Dark, мы теперь продолжим, открыв онлайн-редактор кода, призванный сделать начало работы с Dart очень простым и понятным.

Итак, мы будем использовать инструмент под названием Dart Pad.

Это браузерный инструмент для работы с языком Dart. DartPad не предназначен для использования в профессиональных проектах или чего-то подобного. Это просто инструмент, который поможет вам начать работу с языком и поиграть с очень маленькими фрагментами кода.

Теперь, оказавшись здесь, вы увидите несколько разных панелей на экране, вы увидите некоторый код, заранее сгенерированный слева. Итак, это код, который мы с вами можем писать и редактировать. Затем с правой стороны вы увидите одну панель здесь, в правом верхнем углу. В конечном итоге она покажет нам некоторые выходные данные из темного кода, который мы запускаем. А затем с нижней правой стороны будет некоторая информация о коде, над которым мы работаем здесь.

Вы запускаете DartPad, код программы напишем слева, а затем нажмем на кнопку запуска в самом верху «> Run«, если я нажму на нее прямо сейчас, она выполнит код, который вы видите слева, и мы увидим, что справа появится какой-то вывод.

Мы будем использовать несколько различных функций внутри Dart Pad, чтобы лучше разобраться в коде, который мы пишем.


Первая программа на DART

void main() {
  var name = myName();
  print('My name is $name');
}

String myName() {
  return 'Stephen';
}

Результат выполнения:

My name is Stephen

Мы написали нашу первую маленькую программу Dart, это по сути программа hello world, и я знаю, что hello worlds очень скучны, и вам не очень нравится видеть их внутри курсов.

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

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

Разбор первой программы на Dart

Итак, давайте приступим к делу.


В этой диаграмме я взял эту строку и поместил ее в ряд разных синих квадратиков, так что  мы поговорим о каждом маленьком фрагменте внутри, начиная со слова var на крайней левой стороне слово var объявляет новую переменную (declares a variable). Это один из многих способов, с помощью которых мы можем объявить новую переменную внутри Dart. И мы очень скоро увидим некоторые другие способы, с помощью которых мы можем объявить переменную var.

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

Затем мы разместили наш знак равенства, а затем справа у нас была ссылка на функцию myName() набор скобок после myName вызывает эту функцию или она запускается, она возвращает значение и затем это значение присваивается имени переменной прямо здесь.

Одна вещь, на которую я хочу обратить особое внимание, — это точка с запятой в конце этой строки. Dart требует от нас использование точки с запятой, это является обязательно, поскольку находится внутри JavaScript.

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

У нас есть var, а затем name. Эти два слова прямо здесь образуют шаг, который мы называем объявлением переменной (variable declaration), объявлением переменной это процесс фактического создания этой переменной и сообщения dart, вот новая переменная и вот ее имя.

Затем со знаком равенства и вызовом функции интеллектуального анализа данных у нас есть второй шаг, называемый инициализацией переменной (variable initialization), инициализация — это процесс присвоения значения переменной, которая была объявлена.

Итак, снова два отдельных шага. Первый — это объявление переменной, а второй — инициализация.

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

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

var name = myName();

Помните, имя var называется объявлением переменной, потому что оно создает новую переменную. Затем знак равенства (equals) «=» это называется инициализацией переменной, потому что оно присваивает значение этой новой переменной, которая была только что создана. В конце этой строки у нас был вызов функции myName, которая была определена здесь же, в конце файла.

Начнем рассматривать это объявление функции ниже. Теперь мне нечего особо сказать о функциях, в основном потому, что они работают почти идентично тому, как они работают практически в любом другом языке в стиле C.

String myName() { 
  return 'Stephen'; 
}

Каждой созданной нами функции можно присвоить имя (name of function). Так что в этом случае имя нашей функции — это просто myName. После имени функции мы помещаем набор скобок, которые обозначают список аргументов (argument list). Таким образом, всякий раз, когда мы вызываем функцию, мы можем передать ей некоторое количество аргументов. Затем у нас есть набор фигурных скобок, которые образуют тело функции (function body).

Итак, внутри этих фигурных скобок мы добавляем весь код, который будет выполняться всякий раз, когда мы вызываем эту функцию. А теперь все становится немного интереснее — это слово String, левая сторона(Type of value that will be returned). Поэтому всякий раз, когда наша функция возвращает значение, мы можем аннотировать эту функцию типом значения, которое будет возвращено.

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

Функция main в Dart

Обсудим типы чуть дальше. Но прежде чем мы это сделаем, я хочу упомянуть еще одну вещь о функциях в Dart.

void main() { 
  var name = myName(); 
  print('My name is $name'); 
}

Вернемся сюда. Вы заметите, что у нас была функция с именем main, а затем мы также определили вторую функцию с именем MyName. В общем, мы с вами можем называть функции как угодно, но есть только одно исключение из этого правила, и это функция main.

В Dart функция main — это очень особая функция, которая должна быть определена внутри каждой программы Dart, которую мы когда-либо создавали.

Основная функция здесь автоматически вызывается Dart всякий раз, когда выполняется наша программа. По сути эта точка входа в программу, с нее всегда начинается выполнение программы.

Итак, вы можете представить себе поток операций примерно так.

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

Теперь, одна вещь, которую я хочу здесь упомянуть, заключается в том, что каждая программа Dart, которую мы собираем, должна иметь основную функцию.

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

My name is Stephen.

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

Единственное требование каждой программы Dart заключается в том, что у нас должна быть одна функция с именем main. Единственное, что немного неожиданно в этих функциях, так это то, что они должны аннотировать свой возвращаемый тип, причем тип указывается слева от имени функции.

Основные типы в языке программирования Dart

Поговорим как используются типы в Dart.


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

  • Каждое отдельное значение, которое мы объявляем внутри программы, имеет связанный с ним тип.
  • Точно так же каждая переменная, которую мы объявляем, имеет тип, на который она может ссылаться.

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

var name = myName();

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

Помните, мы говорим var name, которая объявляет нашу переменную, а затем справа мы вызываем функцию myName. Когда мы вызываем myName, мы возвращаем строку, Stephen, прямо здесь.

return 'Stephen';

Так что, по сути, мы с вами можем представить, что эта строка кода

var name = myName();

, как бы эквивалентна

var name = 'Stephen';

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

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

Переменная name может ссылаться на другое значение, которое хранится внутри памяти вашего компьютера. Затем правая часть знака равенства, то есть myName, где мы возвращаем строку Stephen также создает то, что известно как значение внутри памяти. Итак, во всей этой выполненной строке кода мы получаем переменную или ссылку здесь на левой стороне и значение справа.

Значение имеет тип String, связанный с ним, поэтому это здесь, оно имеет тип String.  Вот почему мы говорим, что каждое значение имеет тип, связанный с ним. Но точно так же, эта вещь слева, вещь, которая ссылается на это значение, может иметь тип, связанный с ним также.

Так как эта переменная name здесь указывает на значение с типом String, это неявно означает, что переменная name может ссылаться только на другие значения типа String. Так что я имею в виду, что после того, как мы сделали эту ассоциацию, справа там между name и string  прямо здесь, если я затем создам другое значение внутри моего приложения, например, целое число, один, два, три, я не смогу сделать так, чтобы эта переменная name ссылалась на это целое число, потому что это имя переменная уже связана с типом String.

Теперь, если посмотреть на это в формате диаграммы и дать вам словесное описание, это может немного сбить с толку. Итак, позвольте мне привести вам немного более практический пример обратно внутри DartPad.

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

var name = 'Stephen';

Имени и затем присвоили ему строку, это означает, что имя может ссылаться только на строку. Итак, если я затем попытаюсь присвоить значение, скажем, name = 123;  то я  увижу сообщение об ошибке, в котором говорится: «Эй, вы пытаетесь присвоить значение типа Int переменной типа String».

Итак, снова, когда мы начинаем думать о типах внутри Dart, есть разница между значениями и ссылками. И мы всегда хотим знать о различиях между ними.

Хорошо, давайте продолжим этот список прямо здесь, поэтому мы только что говорили о третьем пункте прямо здесь.

Как только переменная получает связанный с ней тип, как мы делаем

var name = myName();

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

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

Позвольте мне показать вам очень практичный пример этого.

void main() {
 var name = myName();
 print('My name is $name');
 } 
String myName() {
 return 'Stephen'; 
}

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

Так что Dart достаточно умен, чтобы увидеть, что, моя функция myName прямо здесь возвращает значение типа String.

И мы берем это значение типа String и присваиваем его переменной name, чтобы у DART было достаточно информации, посмотрев на вашу программу, можно сделать вывод или как-то предположить, что name должно ссылаться на значение типа String.

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

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

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

Однако у Dart больше нет достаточной информации, чтобы фактически угадать тип имени переменной здесь.

И если вы нажмете на него, вы заметите, что теперь тип помечен как динамический (dynamic name — local variable). Когда вы видите тип динамический, это означает, что Dart по сути говорит, знаете что, я не действительно знаю, какой тип у этой переменной, я собираюсь пометить ее как тип catch all, который является динамическим типом.

Динамический тип по сути означает, что dart просто не знает, какой тип у этой переменной. И поэтому только благодаря тому, что мы с вами, как разработчики, аннотируем некоторые конкретные функции или конкретные переменные внутри нашего приложения, мы даем DART достаточно информации, чтобы точно понять, что происходит.

Итак, если я добавлю строку обратно сюда как аннотацию типа String к функции myName. И снова нажму на myName или name здесь, у Dart снова будет достаточно информации, чтобы выяснить, какой тип должна иметь эта переменная.

Итак, в этом разделе мы обсудили несколько запутанных правил о типах внутри Dart.

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

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

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

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

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

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

Какие плюсы от определения типов данных в Dart

Что дает жесткое определение типов:

  • Улучшение производительности
  • Облегчает работу с крупными проектами
  • Уменьшает необходимость в написании тестов
  • Автоматически находит простые ошибки

Мы говорили  о системах типов в Dart, теперь мы перейдем к самой последней строке кода внутри нашей программы, оператору печати.

 print('My name is $name');

Функция печати — это встроенная функция, которая включена для нас по умолчанию.  Мы используем эту функцию печати для вывода любого конкретного значения внутри нашего приложения.

Итак, в этом случае мы печатаем строку.

My name is

А затем вот это $name.

Я думаю, что в целом оператор печати достаточно прост и работает очень похоже на то, как работают операторы печати в других языках. Итак, давайте немного обратим внимание на строку, где мы вставили $name.

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

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

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

name.length;

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

 print('My name is $name.length');

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

My name is Stephen.length

так что определенно не совсем то, что мы ожидаем.

Синтаксис, который вы видите здесь для знака доллара, а затем переменной, хорош только тогда, когда мы выводим какое-то очень простое значение из переменной, поэтому мы можем использовать только знак доллара и переменную name, если мы просто ссылаемся на значение этой переменной и больше ничего с ней не делаем. Но если мы хотим иметь какой-либо тип выражения, связанный с этой интерполяцией строки, например, поиск свойства в строке, то нам нужно добавить набор фигурных скобок вокруг всего
выражения.

Так что, чтобы этот код заработал, мне нужно было бы добавить знак доллара, фигурную скобку, а затем обернуть все выражение набором фигурных скобок, вот так.

 print('My name is ${name.length}');

Так что теперь, если я запущу этот код, я увижу свое имя, а затем семь, что является количеством символов внутри переменной name.

My name is 7

Итак, еще раз, правило таково: если мы выводим только переменную, мы можем поставить знак доллара, а затем имя переменной. Но если мы собираемся иметь здесь какой-либо тип выражения, мы должны использовать фигурные скобки.

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

 print('My name is ${name}');

Так что если я запущу это сейчас, я вернусь к своему имени Stephen.

My name is Stephen

Итак, если вы хотите быть на 100% в безопасности все время, вы можете просто использовать фигурные скобки со знаком доллара. Но если вы хотите быть немного более изысканными, вы, безусловно, можете опустить фигурные скобки, когда это просто переменная.

Полезные ссылки