Оценок пока нет Классы в языке Dart

Dart — это объектно-ориентированный язык с классами и смешанным наследованием. Каждый объект является экземпляром класса, и все классы происходят от объекта Object. Смешанное наследование означает, что, хотя каждый класс (кроме Object) имеет ровно один суперкласс, тело класса может быть повторно использовано в нескольких иерархиях классов.

Использование членов классов

У объектов есть члены, состоящие из функций и данных (методы и переменные экземпляра соответственно). Когда вы вызываете метод, вы вызываете его для объекта: у метода есть доступ к функциям и данным этого объекта.

Используйте точку (.) Для ссылки на переменную или метод экземпляра:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

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

// If p is non-null, set its y value to 4.
p?.y = 4;
Использование конструкторов

Вы можете создать объект, используя конструктор. Имена конструктора могут быть ClassName или ClassName.identifier. Например, следующий код создает объекты Point с помощью конструкторов Point() и Point.fromJson():

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

Следующий код имеет тот же эффект, но использует необязательное ключевое слово new перед именем конструктора:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
Примечание к версии: новое ключевое слово стало необязательным в Dart 2.

Некоторые классы предоставляют константные конструкторы. Чтобы создать постоянную времени компиляции с помощью конструктора констант, поместите ключевое слово const перед именем конструктора:

var p = const ImmutablePoint(2, 2);

Построение двух идентичных констант времени компиляции приводит к одному, каноническому экземпляру:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

В постоянном контексте вы можете опустить const перед конструктором или литералом. Например, посмотрите на этот код, который создает карту const:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

Вы можете опустить все, кроме первого использования ключевого слова const:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

Если константный конструктор находится вне константного контекста и вызывается без const, он создает неконстантный объект:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!
Примечание к версии: ключевое слово const стало необязательным в постоянном контексте в Dart 2.
Получение типа объекта

Чтобы получить тип объекта во время выполнения, вы можете использовать свойство объектов runtimeType, которое возвращает объект Type.

print('The type of a is ${a.runtimeType}');

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

Переменные экземпляра

Вот как вы объявляете переменные экземпляра:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

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

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

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

Если вы инициализируете переменную экземпляра там, где она объявлена ​​(а не в конструкторе или методе), значение устанавливается при создании экземпляра, то есть перед выполнением конструктора и его списка инициализатора.

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

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

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

Ключевое слово this ссылается на текущий экземпляр.

Примечание. Используйте this только в случае конфликта имен. В противном случае стиль Dart опускает это.

Шаблон назначения аргумента конструктора для переменной экземпляра настолько распространен, что у Dart есть синтаксический сахар, чтобы упростить его:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}
Конструкторы по умолчанию

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

Конструкторы не наследуются

Подклассы не наследуют конструкторы от своего суперкласса. У подкласса, который не объявляет конструкторов, есть только конструктор по умолчанию (без аргумента, без имени).

Именованные конструкторы

Используйте именованный конструктор для реализации нескольких конструкторов для класса или для дополнительной ясности:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

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

Вызов конструктора суперкласса не по умолчанию

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

  1. список инициализаторов
  2. конструктор суперкласса без аргументов
  3. конструктор основного класса без аргументов

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

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

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

Поскольку аргументы конструктора суперкласса оцениваются перед вызовом конструктора, аргумент может быть выражением, таким как вызов функции:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}
Предупреждение: аргументы конструктора суперкласса не имеют доступа к this. Например, аргументы могут вызывать статические методы, но не методы экземпляра.
Список инициализаторов

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

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num=""> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}
Предупреждение: правая часть инициализатора не имеет доступа к этому.

Во время разработки вы можете проверить входные данные, используя assert в списке инициализатора

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

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

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
Перенаправление конструкторов

Иногда единственной целью конструктора является перенаправление в другой конструктор того же класса. Тело перенаправляющего конструктора пустое, а вызов конструктора появляется после двоеточия (:)

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
Константные конструкторы

Если ваш класс создает объекты, которые никогда не меняются, вы можете сделать эти объекты константами времени компиляции. Для этого определите конструктор const и убедитесь, что все переменные экземпляра являются финальными final

class ImmutablePoint {
  static final ImmutablePoint origin = const ImmutablePoint(0, 0);
  final num x, y;
  const ImmutablePoint(this.x, this.y);
}

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

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

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

В следующем примере демонстрируется конструктор фабрики, возвращающий объекты из кэша:

class Logger {
  final String name;
  bool mute = false;
  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, logger=""> _cache = <String, logger="">{};
  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }
  Logger._internal(this.name);
  void log(String msg) {
    if (!mute) print(msg);
  }
}
Примечание: фабричные конструкторы не имеют доступ к this.

Вызовите конструктор фабрики так же, как любой другой конструктор:

var logger = Logger('UI');
logger.log('Button clicked');

Методы

Методы — это функции, которые обеспечивают поведение объекта.

Методы экземпляра

Методы экземпляра на объектах могут обращаться к переменным экземпляра, и this. Метод distanceTo() в следующем примере является примером метода экземпляра:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
Геттеры и сеттеры

Методы получения и установки — это специальные методы, обеспечивающие доступ на чтение и запись к свойствам объекта. Напомним, что каждая переменная экземпляра имеет неявный метод получения, а также, в случае необходимости, метод установки. Вы можете создать дополнительные свойства, реализовав методы получения и установки, используя ключевые слова get и set:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

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

Примечание. Такие операторы, как increment (++), работают ожидаемым образом, независимо от того, определен ли получатель явно. Чтобы избежать неожиданных побочных эффектов, оператор вызывает геттер ровно один раз, сохраняя его значение во временной переменной.
Абстрактные методы

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

Чтобы сделать метод абстрактным, используйте точку с запятой (;) вместо тела метода:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}
Абстрактные классы

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

Абстрактные классы часто имеют абстрактные методы. Вот пример объявления абстрактного класса, который имеет абстрактный метод:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}
Неявные интерфейсы

Каждый класс неявно определяет интерфейс, содержащий все элементы экземпляра класса и любые интерфейсы, которые он реализует. Если вы хотите создать класс A, который поддерживает API класса B, не наследуя реализацию B, класс A должен реализовать интерфейс B.

Класс реализует один или несколько интерфейсов, объявив их в предложении Implements, а затем предоставив API-интерфейсы, требуемые интерфейсами. Например:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

Вот пример указания, что класс реализует несколько интерфейсов:

class Point implements Comparable, Location {...}
Расширение класса

Используйте extends для создания подкласса, и super для ссылки на суперкласс:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
Переопределяющие члены

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

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

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

<+|[]
>/^[]=
<=~/&~
>=*<<==
%>> 
Примечание. Возможно, вы заметили, что != Не является переопределенным оператором. Выражение e1! = E2 является просто синтаксическим сахаром для !(E1 == e2).

Вот пример класса, который переопределяет операторы + и -:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

Если вы переопределите ==, вам также следует переопределить метод получения hashCode объекта. Пример переопределения == и hashCode см. В разделе «Реализация ключей карты».

Для получения дополнительной информации о переопределении, в общем, см. Расширение класса.

Метод noSuchMethod()

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

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

Вы не можете вызвать нереализованный метод, если не выполнено одно из следующих условий:

  • Приемник имеет статический тип динамический dynamic.
  • Получатель имеет статический тип, который определяет нереализованный метод (аннотация в порядке), а динамический тип получателя имеет реализацию noSuchMethod(), которая отличается от реализации в классе Object.

Для получения дополнительной информации см. Неофициальную спецификацию пересылки noSuchMethod.

Перечисляемые типы

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

Использование перечислений

Объявите перечислимый тип, используя ключевое слово enum:

enum Color { red, green, blue }

Каждое значение в перечислении имеет получатель индекса, который возвращает позицию значения, начинающуюся с нуля, в объявлении перечисления. Например, первое значение имеет индекс 0, а второе значение имеет индекс 1

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

Чтобы получить список всех значений в перечислении, используйте константу значений values перечисления

List<color> colors = Color.values;
assert(colors[2] == Color.blue);

Вы можете использовать enums в выражениях switch и вы получите предупреждение, если вы не обработаете все значения enum:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

Перечисляемые типы имеют следующие ограничения:

  • Вы не можете создавать подклассы, смешивать или реализовывать перечисление.
  • Вы не можете явно создать экземпляр enum.

Для получения дополнительной информации см. Спецификацию языка Dart.

Добавление возможностей в класс: mixins

Миксины — это способ многократного использования кода класса в нескольких иерархиях классов.

Чтобы использовать миксин, используйте ключевое слово with, за которым следует одно или несколько имен миксинов. В следующем примере показаны два класса, которые используют mixins:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

Чтобы реализовать миксин, создайте класс, который расширяет Object и не объявляет конструкторов. Если вы не хотите, чтобы ваш миксин использовался как обычный класс, используйте ключевое слово mixin вместо class. Например:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

Чтобы указать, что только некоторые типы могут использовать миксин — например, чтобы ваш миксин мог вызывать метод, который он не определяет — используйте on для указания требуемого суперкласса:

mixin MusicalPerformer on Musician {
  // ···
}
Примечание к версии: Поддержка ключевого слова mixin была введена в Dart 2.1. Код в более ранних выпусках обычно использовал abstract class. Дополнительную информацию об изменениях в миксинах 2.1 см. В журнале изменений Dart SDK и спецификации миксинов 2.1.

Переменные и методы класса

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

Статические переменные

Статические переменные (переменные класса) полезны для общеклассового состояния и констант:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

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

Примечание. На этой странице соблюдается рекомендация руководства по стилю о предпочтении lowerCamelCase для постоянных имен.
Статические методы

Статические методы (методы класса) не работают с экземпляром и, следовательно, не имеют доступа к this. Например:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 > distance && distance > 2.9);
  print(distance);
}
Примечание: рассмотрите возможность использования функций верхнего уровня вместо статических методов для общих или широко используемых утилит и функций.

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

Пожалуйста, оцените материал

WebSofter

Web - технологии