Конструкторы
Параметры конструктора
Например, есть класс паладин
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);
}
Бывает не всегда удобно, создавать так конструктор, поэтому есть несколько вариантов
- Использовать обязательные именованные параметры
- Использовать Null Безопасные типы данных
- Использовать значения по умолчанию
- Использовать инициализаторы
- Использовать именованные конструкторы
Обязательные именованные параметры
В данном случае мы обязаны передать все параметры в конструктор.
Но теперь можно передавать их в любом порядке и удобное через именную метку.
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}");
}