Представляем языковые функции Dart 2: mixins, enumsи многое другое

Представляем языковые функции Dart 2 Программирование и разработка

Dart — это объектно-ориентированный, основанный на классах, простой и чистый язык. Популярная мобильная платформа Google Flutter использует Dart для реализации высококачественных нативных приложений. В 2018 году был выпущен Dart 2 со множеством новых функций, которые сделали его намного безопаснее, выразительнее и удобнее.

Это обновление изменило основы языка Dart, библиотек, системы сборки и инструментов веб-разработки.

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

Dart

Расширения Dart

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

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

Расширения определяются внутри одного блока кода, который начинается с extensionи содержит имя расширения, onключевое слово и тип данных. Dart поддерживает три типа расширений, которые мы рассмотрим более подробно ниже:

extension<T> on List<T> {
  //extension methods
  //extension operators
  //extension properties
}

Методы расширения

Методы расширения позволяют добавлять новые элементы к существующим типам. Возможно, вы уже используете методы расширения. Например, при использовании автозавершения кода в среде IDE будет предложен метод расширения. Методы расширения находятся в библиотеках.

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

Давайте посмотрим на пример, чтобы понять методы расширения. Ниже мы напишем метод расширения для Listтипа данных. Метод расширения priceList()возвращает прайс в том же состоянии. Этот метод демонстрирует, как расширения неявно расширяют тип с помощью this.

//Local extension method
extension<T> on List<T> {
  //Extension Method demonstration
  List<T> priceList() => this.map((item) => item).toList();
}
void main() {
  //List of prices
  List prices = [1, 1.99, 4];
  print(«Price listing:»);
  //priceList() is being called on `prices` list
  //and returns the list of prices
  print(prices.priceList());
}

Операторы расширения

Dart также поддерживает операторов. Ниже мы определим операторное расширение для ^оператора. Мы предполагаем, что этот оператор увеличивает цену в nраз, где n- переданный аргумент.

operatorКлючевое слово объявляет оператор продолжения. За ним следует знак оператора. В следующем примере thisсписок повторяется для умножения каждого элемента на n, а затем возвращается обновленный список.

extension<T> on List<T> {
  //Extension Operator: Hike up the price by n
  List<num> operator ^(int n) =>
    this.map((item) => num.parse("${item}") * n).toList(); 
}

Оператор ^применяется к pricesсписку. Оператор ^умножает каждый элемент pricesна 3 и возвращает. Обновленный список затем распечатывается с использованием этого print()метода.

void main() {
  //List of prices
  List prices = [1, 1.99, 4];
 
  print("\nPrice listing after hiking up prices 3x of the original value");
 
  //argument is passed after the operator sign
  print(prices ^ 3);
}

Это дает нам результат ниже:

Price listing after hiking up prices 3x of the original value
[3, 5.97, 12]

Свойство расширения

Dart также обеспечивает поддержку свойств. В расширении ниже мы добавляем свойство, которое возвращает общее количество напечатанных ценников, необходимых для составления прайс-листа. Предположим, мы хотим напечатать 3 этикетки для каждой суммы цены в списке [1, 1.99, 4].

Определение свойства расширения состоит из трех частей: типа данных, возвращаемых свойством, getключевого слова и имени свойства. Например:

<return_type> get <property_name> => //implementation

Количество меток относится к типу int, а имя свойства — labelCount. Нам нужно по 3 метки для каждого элемента, поэтому нам потребуется втрое больше общего размера списка. Мы можем рассчитать общее количество меток как length * 3. Расширение имеет неявный доступ к lengthсвойству.

extension<T> on List<T> {
  //Extension Property: 3 printed labels for each price.
  int get labelCount => length * 3;
}

Недвижимость labelCountуказана в прайс-листе prices.

void main() {
  //List of prices
  List prices = [1, 1.99, 4];
 
  print("\nNumber of Printed Labels:");
  print(prices.labelCount);
}

Это дает нам следующий результат:

Number of Printed Labels:
9

Dart Enums

Перечислимые типы (также известные как Enums) были добавлены с выпуском Dart 1.8. Перечисления действуют как класс, представляющий фиксированное количество постоянных значений. Например, у вас может быть приложение, которое получает данные с удаленного сервера. Приложение показывает один из следующих статусов:

  • done: приложение успешно получило ответ.
  • waiting: приложение ожидает ответа.
  • error: приложение получило ошибку от сервера.

Вышеуказанные ответы можно объявить с помощью enumключевого слова:

enum Status {
  done,
  waiting,
  error, //This comma is optional
}

Давайте лучше поймем это на примере и представим, что у нас есть погодное приложение. Нам нужно представить состояния погоды: солнечную, облачную и дождливую.

Мы могли бы представить эти состояния с помощью constключевого слова.

const SUNNY = 'Sunny';
const CLOUDY = 'Cloudy';
const RAINY = 'Rainy';

Или мы могли бы использовать перечислимые типы с enumключевым словом.

enum Weather {
  sunny,
  cloudy,
  rainy,
}

Переключить блок с помощью Enums

Мы можем использовать switchблок для перечислений, и он требует блоков case для всех членов нашего enumкласса и defaultблока, если реализация блока case отсутствует, в противном случае вы получите ошибку компиляции. Взгляните на пример ниже.

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

//Using Enums to display weather information
enum Weather {
  sunny,
  cloudy,
  rainy,
}
void main() {
  var weather = Weather.sunny;
  //Following code will complain about if all members are not present
  //Use default-block when all case-blocks are not available
  switch (weather) {
    case Weather.sunny:
      print(«Sunny weather today!»);
      break;
    case Weather.cloudy:
      print(«Cloudy today!»);
      break;
    case Weather.rainy:
      print(«Rainy and gloomy weather.»);
      break;
    default:
      print(«Current weather:${weather}»);
  }
}

Dart Mixins

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

Миксины объявляются с использованием mixinключевого слова:

mixin SharedBehavior {
 }

Синтаксис Mixins прост. Ниже Bпоказан родительский класс для A. Cэто миксин с методами, которые Bможно реализовать.

class A extends B with C {
    //Implement methods from B & C

}

Пример Mixins

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

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

Перекрывающиеся общие поведения, подобные этим, могут быть извлечены в миксины. Мы создадим миксины для этих поведений. Например, Sketchingмиксин определяет общий sketch()метод. Этот метод принимает messageпараметр.

mixin Sketching {
  sketch(String message) {
    print(message);
  }
}

Посмотрим на наш пример в коде. Ниже в main()методе создается объект каждого класса и вызываются методы для их общего поведения, которое реализуется с помощью миксинов.

//Person class
abstract class Person {
  int age;
  int name;
  eat() {}
  sleep() {}
}
//Artist class
class Artist extends Person with Sketching {
  sketchLandscape() {
    sketch(«Making landscapes sketches»);
  }
}
//Engineer class
class Engineer extends Person with Sketching, Reading {
  sketchBuildings() {
    sketch(«Sketching engineering drawings»);
  }
  readResearchPaper() {
    String topic = «Building Construction»;
    dailyReading(topic);
  }
}
//Doctor class
class Doctor extends Person with Reading, Exercise {
  readReports() {
    String topic = «flu»;
    dailyReading(topic);
  }
  workout() {
    running(1);
    weightTraining(10);
  }
}
//Athlete class
class Athlete extends Person with Exercise {
  generalRoutine() {
    running(2);
    weightTraining(20);
  }
}
//Boxer class
class Boxer extends Athlete with Boxing {
  punchPractice() {
    punch(100);
  }
  routineExercise() {
    running(4);
    weightTraining(40);
  }
}
//Mixins
//Sketching mixin
mixin Sketching {
  sketch(String message) {
    print(message);
  }
}
//Reading mixin
mixin Reading {
  dailyReading(String topic) {
    print(«Daily reading on ${topic}»);
  }
}
//Exercise mixin
mixin Exercise {
  running(int mile) {
    print(«Daily run of ${mile} mile(s)»);
  }
  weightTraining(int weights) {
    print(«Lifting ${weights} lbs»);
  }
}
//Boxing
mixin Boxing on Athlete {
  punch(int n) {
    print(«Boxer practicing ${n} punches»);
  }
}
void main() {
  print(«Artist»);
  Artist artist = Artist();
  artist.sketchLandscape();
  print(«\nEngineer»);
  Engineer engineer = Engineer();
  engineer.sketchBuildings();
  engineer.readResearchPaper();
  print(«\nDoctor»);
  Doctor doctor = Doctor();
  doctor.readReports();
  doctor.workout();
  print(«\nBoxer»);
  Boxer boxer = Boxer();
  boxer.punchPractice();
  boxer.routineExercise();
}

Dart Generics

Обобщения используются для применения более строгих проверок типов во время компиляции. Они также обеспечивают безопасность типов. Обобщения помогают писать повторно используемые классы и методы / функции для разных типов данных. Дженерики в Dart похожи на дженерики Java или шаблоны C ++.

Определение: Безопасность типов — это концепция программирования, которая позволяет блоку памяти содержать только один тип данных.

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

Давайте узнаем, как объявлять типобезопасные коллекции. Чтобы обеспечить безопасность типа, угловые скобки <>с заключенным типом данных объявляют коллекцию данного типа данных.

CollectionType <dataType> identifier = CollectionType <dataType>();

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

  • E: представляет тип элемента в коллекции, т.е.List
  • R: представляет тип возвращаемого значения функции или метода
  • K: представляет ключевой тип в ассоциативных коллекциях, т.е.Map
  • V: представляет тип значения в ассоциативных коллекциях, т.е.Map

Примечание. Мы также можем представлять универсальные шаблоны с описательными именами, такими как Productили. Inventory Взгляните на пример реализации универсальных шаблонов с однобуквенными именами.

import ‘dart:collection’;
//Demonstrating use of single letter for generics
//A class for grocery product
class Product {
  final int id;
  final double price;
  final String title;
  Product(this.id, this.price, this.title);
  @override
  String toString() {
    return «Price of ${this.title} is \$${this.price}»;
  }
}
//A class for product’s inventory
class Inventory {
  final int amount;
  Inventory(this.amount);
  @override
  String toString() {
    return «Inventory amount: $amount»;
  }
}
//Custom type variables- Single letter
class Store<P, I> {
  final HashMap<P, I> catalog = HashMap<P, I>();
  List<P> get products => catalog.keys.toList();
  void updateInventory(P product, I inventory) {
    catalog[product] = inventory;
  }
  void printProducts() {
    catalog.keys.forEach(
      (product) => print(«Product: $product, » + catalog[product].toString()),
    );
  }
}
//Demonstrating single letter
void main() {
  Product milk = Product(1, 5.99, «Milk»);
  Product bread = Product(2, 4.50, «Bread»);
  //Using single letter names for Generics
  Store<Product, Inventory> store1 = Store<Product, Inventory>();
  store1.updateInventory(milk, Inventory(20));
  store1.updateInventory(bread, Inventory(15));
  store1.printProducts();
}

Асинхронность в Dart

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

Dart предоставляет два способа асинхронной обработки событий / запросов. Дарта dart:asyncбиблиотека обеспечивает поддержку асинхронного программирования с Futureи Streamклассами.

  • Будущие объекты: представляют результаты асинхронных операций. Это похоже на обещание результата в будущем.
  • Объекты потока: предоставляет последовательность событий, которые являются либо значением, либо ошибкой.

Future объекты

Давайте изучим объекты будущего. Результаты асинхронных операций возвращаются в виде Futureобъектов. Будущий объект представлен как Future, где T- тип возвращаемых результатов. Когда нам нужен результат завершенного будущего, мы можем использовать либо:

  • await and async
  • The Future API

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

Программа приостанавливается при awaitвызове, возврате функции или достижении конца функции. Давайте посмотрим на пример asyncи awaitниже:

// Expensive function could be a function that takes
// long time to process data and return results.
// Assume this function takes long time to return in real-world
String getExpansiveData() {
  return «I’m expansive data»;
}
// This is the asynchronous function that makes the expensive
// data call and prints the results.
Future<void> makeDataCall() async {
  var data = await getExpansiveData();
  print(data);
}
//—-END—-//
//Entry point function
void main() {
  makeDataCall();
}

Использование ключевых слов await и async для запуска асинхронной операции

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

В makeDataCall()возвращает Futureтипа, voidтак как нет ничего, возвращенное функцией. Он вызывает getExpansiveData()метод с ключевым словом await, которое возвращает строку I’m expansive data. Метод makeDataCall()печатает результаты с функцией print().

FutureAPI также может быть использован для выполнения асинхронных операций. В FutureAPI then()метод регистрирует обратный вызов, который запускается после завершения Future.

Есть два варианта Future API.

  • Future<String>: Будущий возвращаемый Stringтип данных.
  • Future<void>: Будущее возвращается void.

Что изучать дальше

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

Но еще многое предстоит узнать о Dart 2, чтобы по-настоящему использовать этот язык в полной мере. Следующие шаги вашего обучения — это следующие концепции:

  • Вызываемые классы
  • Функции генератора
  • Общие коллекции
  • API и потоки в Dart
  • Переменные ОС / PlatformКласс
Читайте также:  Полиморфизм в Java
Оцените статью
bestprogrammer.ru
Добавить комментарий