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

Параметры конструктора

Например, есть класс паладин

Dart - Класс Paladin

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  Paladin(this.name, this.hp, this.mana);

  void attack() {}
  void heal() {}
}

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

👉 сначала name потом hp потом mana

Dart - Создание объекта

Светлая тема Темная тема
void main() {
  Paladin paladin = Paladin("Паладин", 100, 50);
}

Бывает не всегда удобно, создавать так конструктор, поэтому есть несколько вариантов

  1. Использовать обязательные именованные параметры
  2. Использовать Null Безопасные типы данных
  3. Использовать значения по умолчанию
  4. Использовать инициализаторы
  5. Использовать именованные конструкторы

Обязательные именованные параметры

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

Dart - Обязательные именованные параметры

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  Paladin({
    required this.name,
    required this.hp,
    required this.mana,
  });

  void attack() {}
  void heal() {}
}

void main() {
  Paladin paladin = Paladin(
    name: "Паладин",
    hp: 100,
    mana: 50,
  );
}

Null Безопасные типы данных

Dart - Null Безопасные типы

Светлая тема Темная тема
class Paladin {
  String? name;
  int? hp;
  int? mana;

  Paladin( {this.name, this.hp, this.mana} );

  void attack() {}
  void heal() {}
}

void main() {
  Paladin paladin = Paladin(name: "Паладин", hp: 100);

  print(paladin.mana); // null
}

Значения по умолчанию

Dart - Необязательные параметры

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  // Необязательные параметры с значением по умолчанию
  Paladin([
    this.name = "Пал Палыч",
    this.hp = 0,
    this.mana = 0,
  ]);

  void attack() {}
  void heal() {}
}

void main() {
  Paladin paladin = Paladin();
  print(paladin.name); // Пал Палыч
}

Dart - Именованные параметры

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  // Именованные параметры с значением по умолчанию
  Paladin({
    this.name = "Пал Палыч",
    this.hp = 0,
    this.mana = 0,
  });

  void attack() {}
  void heal() {}
}

void main() {
  Paladin paladin = Paladin(mana: 200);
  print(paladin.name); // Пал Палыч
}

Инициализаторы

ВАЖНО: Правая часть инициализатора не имеет доступа до this

Dart - Инициализаторы

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  Paladin() : name = "Пал Палыч", hp = 0, mana = 0;

  void attack() {}
  void heal() {}
}

void main() {
  Paladin paladin = Paladin();

  print(paladin.name); // Пал Палыч
}

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

У класса может быть несколько конструкторов + главный конструктор по умолчанию который скрытый.

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

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  Paladin({required this.name}) : hp = 0, mana = 0;
  Paladin.dark({required this.name}) : hp = 1500, mana = 1000;
  Paladin.light({required this.name}) : hp = 1000, mana = 1200;

  void attack() {}
  void heal() {}

  @override
  String toString() {
    super.toString();
    return "Имя = $name, здоровье = $hp, мана = $mana";
  }
}

void main() {
  Paladin paladin = Paladin(name: "Пал Палыч");
  Paladin darkPaladin = Paladin.dark(name: "Падший Пал Палыч");
  Paladin lightPaladin = Paladin.light(name: "Просветленный Пал Палыч");

  print(paladin);
  print(darkPaladin);
  print(lightPaladin);
}

Результат

Светлая тема Темная тема
Имя = Пал Палыч,               здоровье = 0,    мана = 0
Имя = Падший Пал Палыч,        здоровье = 1500, мана = 1000
Имя = Просветленный Пал Палыч, здоровье = 1000, мана = 1200

 ООП Часть 4

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

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

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

Контроль над созданием объекта.

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

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

Светлая тема Темная тема
class Paladin {
  String name;
  int hp;
  int mana;

  // Приватный конструктор
  Paladin._internal(this.name, this.hp, this.mana);

  // Фабричный конструктор
  factory Paladin({required String type, required String name}) {
    if (type == 'dark') {
      return Paladin._internal(name, 1500, 1000);
    } else if (type == 'light') {
      return Paladin._internal(name, 1000, 1200);
    } else {
      return Paladin._internal(name, 0, 0);
    }
  }

  void attack() {}
  void heal() {}

  @override
  String toString() {
    return "Имя = $name, здоровье = $hp, мана = $mana";
  }
}

void main() {
  Paladin darkPaladin = Paladin(type: 'dark', name: "Падший Пал Палыч");
  Paladin lightPaladin = Paladin(type: 'light', name: "Просветленный Пал Палыч");

  print(darkPaladin);
  print(lightPaladin);
}

Шаблон Синглтон

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

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

Dart - Шаблон Синглтон

Светлая тема Темная тема
class Settings {
  static Settings? _instance;

  String language = "en";

  // Фабричный конструктор всегда возвращает один и тот же объект
  factory Settings() {
    // Если экземпляр уже создан, вернуть его, иначе создать новый
    return _instance ??= Settings._internal();
  }

  // Приватный конструктор для предотвращения создания новых объектов
  Settings._internal();
}

Dart - Использование Синглтона

Светлая тема Темная тема
void main() {
  var settings1 = Settings();
  var settings2 = Settings();

  settings1.language = "ru"; // Меняем язык в одном объекте
  print(settings2.language); // ru (значение изменилось в обоих объектах)
}

Фабричный конструктор с кэшированием

Иногда выгодно повторно использовать уже созданные объекты, например, при работе с базой данных или создании ресурсов.

Dart - Кэширование

Светлая тема Темная тема
class User {
  final String username;
  static final Map<String, User> _cache = {};

  // Фабричный конструктор возвращает уже созданный объект
  factory User(String username) {
    return _cache.putIfAbsent(username, () => User._internal(username));
  }

  User._internal(this.username);
}

void main() {
  var user1 = User("Юля");
  var user2 = User("Юля");
  var user3 = User("Артём");

  print(identical(user1, user2)); // true (Юля уже есть в кэше)
  print(identical(user1, user3)); // false (Артём создаётся отдельно)
}

Теперь, если объект уже есть в кэше, фабричный конструктор не создаёт новый экземпляр, а возвращает старый!

Асинхронный фабричный конструктор

Асинхронное программирование будет в Главе 8

Допустим, нам нужно загружать данные из сети перед созданием объекта. В обычном конструкторе async использовать нельзя, но в фабричном — можно!

Dart - Асинхронный конструктор

Светлая тема Темная тема
class UserProfile {
  String name;
  int level;

  UserProfile._(this.name, this.level);

  // Асинхронный фабричный конструктор
  static Future<UserProfile> create(String userId) async {
    print("Загружаем данные о пользователе $userId...");
    await Future.delayed(Duration(seconds: 2)); // Имитация запроса в базу данных

    return UserProfile._("Герой $userId", 10 + userId.length);
  }
}

void main() async {
  var user = await UserProfile.create("player_123");
  print("Создан профиль: ${user.name}, уровень ${user.level}");
}