конструктор

Что такое конструктор

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

Типы конструкторов в Dart:

  1. Генеративный конструктор (Generative)
  2. Конструктор по умолчанию (Default)
  3. Именованный конструктор (Named)
  4. Константный конструктор (Const)
  5. Перенаправляющий конструктор (Redirecting)
  6. Фабричный конструктор (Factory)
  7. Перенаправляющий фабричный конструктор
  8. Сокращенный синтаксис конструктора (Dart 3.13+)
  9. Основной конструктор (Primary Constructors, компактные поля + синтаксис конструктора)

1. Генеративный конструктор Generative Constructor

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

class Point {
  double x;
  double y;

  // Генеративный конструктор, this.x напрямую принимает параметр для присваивания члену
  Point(this.x, this.y); // Сразу присваивает значения x и y
}

void main() {
  final p = Point(10, 20);
  print("(${p.x}, ${p.y})"); // (10, 20)
}
Code language: JavaScript (javascript)

2. Конструктор по умолчанию Default Constructor

Если в классе не определён вручную ни один конструктор, Dart автоматически предоставляет безымянный генеративный конструктор без параметров. Поведение совпадает с другими C-подобными языками: C++, Java, C# и т.д.

class PointA {
  double x = 0;
  double y = 0;
  // Неявный конструктор по умолчанию: PointA();
}

void main() {
  final p = PointA(); // Автоматический вызов конструктора по умолчанию
  print("(${p.x}, ${p.y})"); // (0.0, 0.0)
}
Code language: PHP (php)

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

3. Именованный конструктор Named Constructor

Назначение: определить несколько конструкторов в одном классе с понятной семантикой; синтаксис ИмяКласса.ИмяКонструктора()

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // Основной генеративный конструктор
  Point(this.x, this.y);

  // Именованный конструктор: начало координат
  Point.origin() : x = xOrigin, y = yOrigin;
}

void main() {
  final p1 = Point(5, 6);
  final p2 = Point.origin(); // Вызов именованного конструктора
  print(p1.x); //5
  print("Начало координат: (${p2.x}, ${p2.y})"); // (0,0)
}
Code language: PHP (php)

Ключевые правила

  • Подклассы не наследуют именованные конструкторы родительского класса; если нужен аналогичный именованный конструктор в подклассе — его нужно реализовать вручную;
  • Именованные конструкторы поддерживают список инициализации для присваивания значений final-переменным.

4. Константный конструктор Const Constructor

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

  1. Все членские переменные помечены модификатором final
  2. Перед конструктором указать ключевое слово const. При создании экземпляра с const используется кэш констант, без const создаётся отдельный обычный объект.
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0); // Статический общий объект для всего класса
  final double x, y;

  // Константный конструктор инициализирует final x и y
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Константа компиляции, повторно используется тот же объект
  const p1 = ImmutablePoint(1, 2);
  const p2 = ImmutablePoint(1, 2); // При одинаковых параметрах экземпляр общий
  print(p1 == p2); // true

  // Обычный экземпляр, новый отдельный объект
  final p3 = ImmutablePoint(1, 2);
  print(p1 == p3); // false
}Code language: PHP (php)

5. Перенаправляющий конструктор Redirecting Constructor

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

class Point {
  double x, y;

  // Основной конструктор
  Point(this.x, this.y);

  // Перенаправляющий конструктор: принимает только x, y фиксирован как 0, перенаправляет на основной конструктор
  Point.alongX(double x) : this(x, 0); // Вызывает именованный конструктор, передаёт x основной конструкту и фиксирует y=0
}

void main() {
  final p = Point.alongX(9);
  print("x=${p.x}, y=${p.y}"); // x=9, y=0
}
Code language: JavaScript (javascript)

6. Фабричный конструктор Factory Constructor

Отмечен ключевым словом factory, сценарии применения:

  1. Не обязательно создаёт новый объект (повторное использование кэша, возврат экземпляров подклассов)
  2. Сложная логика перед созданием объекта (парсинг JSON, проверка параметров). Ограничение: нельзя обращаться к this
Пример синглтон-логгера с кэшем
class Logger {
  final String name;
  bool mute = false;
  // Приватный кэш, виден только внутри библиотеки
  static final Map<String, Logger> _cache = {};

  // Фабричный конструктор: сначала берёт из кэша
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // Фабричный конструктор: создание из разобранного JSON
  factory Logger.fromJson(Map<String, dynamic> json) {
    return Logger(json["name"].toString());
  }

  // Внутренний приватный генеративный конструктор
  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

void main() {
  final log1 = Logger("UI");
  final log2 = Logger("UI");
  print(log1 == log2); // true, один объект из кэша

  final jsonLog = Logger.fromJson({"name": "Net"});
  jsonLog.log("request success");
}
Code language: JavaScript (javascript)

7. Перенаправляющий фабричный конструктор Redirecting Factory

Синтаксис factory Имя() = КонструкторДругогоКласса;. Подходит для абстрактных классов, делегирующих создание экземпляров, избавляет от повторного описания параметров.

abstract class Listenable {}

class _MergingListenable implements Listenable {
  List<_MergingListenable> list;
  _MergingListenable(this.list);
}

// Перенаправляющая фабрика, вызывает конструктор внутреннего приватного класса
factory Listenable.merge(List<Listenable> list) = _MergingListenable;
Code language: PHP (php)

8. Отделение конструкторов Constructor Tear-offs

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

Рекомендуемый синтаксис
void main() {
  final charCodes = [65, 66, 67];
  // Именованный конструктор String.fromCharCode
  final strs = charCodes.map(String.fromCharCode);
  print(strs); // (A, B, C)

  // Отделение безымянного конструктора через .new
  final buffers = charCodes.map(StringBuffer.new);
}
Code language: PHP (php)
Не рекомендуется: лишняя лямбда
// Лишняя обёртка, не используйте
charCodes.map((code) => String.fromCharCode(code));
Code language: JavaScript (javascript)

9. Сокращённый синтаксис конструктора (Dart 3.13+) Concise Constructor

Внутри класса можно опустить имя класса, сразу использовать new / factory для определения конструкторов без записи ИмяКласса.xxx:

Традиционный синтаксисСокращение
Point(this.x,this.y)new(this.x,this.y)
Point.origin():x=0,y=0new origin():x=0,y=0
factory Point.clone()factory clone()
class Point {
  double x, y;

  // Сокращённый безымянный генеративный конструктор
  new(this.x, this.y);

  // Сокращённый именованный генеративный конструктор
  new origin() : x = 0, y = 0;

  // Сокращённый фабричный конструктор
  factory clone(Point other) => new(other.x, other.y);
}

void main() {
  final p = new(3, 4);
  final origin = new origin();
  final copy = p.clone(p);
}
Code language: PHP (php)

10. Три способа инициализации экземплярных переменных

1: Прямое присваивание при объявлении
class PointA {
  double x = 1.0;
  double y = 2.0;
}
Code language: JavaScript (javascript)

2: Инициализирующие параметры

this.переменная

Поддерживает позиционные опциональные параметры, именованные параметры и nullable-переменные

class PointB {
  final double x;
  final double y;

  // Позиционные опциональные параметры
  PointB.opt([this.x = 0, this.y = 0]);

  // Именованные параметры со значением по умолчанию
  PointB.named({this.x = 5, this.y = 5});
}
Code language: JavaScript (javascript)

3: Список инициализации Initializer List

Присваивание после двоеточия :, выполняется раньше тела конструктора, нельзя обращаться к this, поддерживает assert для проверки параметров.

class Point {
  final double x;
  final double y;

  Point.fromJson(Map<String, double> json)
      : x = json["x"]!,
        y = json["y"]! {
    print("Выполнено тело конструктора, x=$x");
  }

  // С проверкой через assert
  Point.withAssert(this.x, this.y) : assert(x >= 0);
}
Code language: JavaScript (javascript)

11. Прямые инициализирующие параметры для приватных полей (Dart3.12+)

Для приватных переменных с префиксом подчёркивания параметры конструктора записываются как this._поле. При внешнем вызове используется публичное имя без подчёркивания.

class Point {
  final double _x;
  final double _y;

  // Внутри используется _x, при вызове снаружи пишут x:10
  Point({required this._x, this._y = 0});
}

void main() {
  final p = Point(x: 10); // Не нужно писать _x, достаточно x
  print(p._x); // 10
}
Code language: PHP (php)

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

class Point {
  final double _x;
  Point({required this._x}) : assert(_x > 0);
}
Code language: JavaScript (javascript)

12. Конструкторы родительского класса и правила наследования

  1. Подклассы не наследуют никакие конструкторы родительского класса;
  2. Порядок выполнения конструктора: список инициализации → конструктор родителя → тело конструктора подкласса;
  3. Если у родительского класса нет безпараметрического конструктора по умолчанию, подкласс должен вручную вызвать super() для указания родительского конструктора.
Простой пример вызова super

Как и в Java, для ссылки на базовый класс используется super, в C# — base.

class Person {
  final String name;
  Person(this.name);
  Person.fromMap(Map map) : name = map["name"];
}

class Employee extends Person {
  int id;
  // Вызов именованного конструктора родителя
  Employee(Map data) : super.fromMap(data), id = data["id"];
}
Code language: JavaScript (javascript)

Важно: в выражениях параметров super нельзя использовать this.

Параметры Super (Dart2.17+, упрощают передачу аргументов родителю)

Используйте super.переменная для прямой передачи параметров родительскому конструктору без ручного написания super(x,y)

class Vector2d {
  final double x, y;
  Vector2d(this.x, this.y);
}

class Vector3d extends Vector2d {
  final double z;
  // super.x super.y автоматически передаются родительскому конструкту
  Vector3d(super.x, super.y, this.z);
}

// Именованный конструктор родителя + именованные super-параметры
class Vector3dYZ extends Vector2d {
  final double z;
  Vector3dYZ({required super.y, required this.z}) : super.named(x: 0);
}
Code language: JavaScript (javascript)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *