По умолчанию конструкторы класса создают и возвращают новый объект этого класса. Но, возможно, не всегда потребуется создавать новый объект класса. Возможно, мы захотим использовать и возвращать из конструктора уже имеющийся объект. Для этой цели в языке Dart применяются фабричные конструкторы или конструкторы, которые предваряются ключевым словом factory.
Фабричный конструктор позволяет выполнить некоторую начальную логику непосредственным созданием объекта. В частности, можно выполнить проверку и валидающи переданных значений, их модификацию, какие-то другие действия, которые необходимы перед созданием объекта.
class Person{ String name; int age; factory Person(String name, int age) { if(name.length <2) { name = "Undefined"; } if(age <1 || age > 110) { age = 18; } return Person._create(name, age); } Person._create(this.name, this.age); void display() => print("Name: $name \tAge: $age"); } void main() { Person tom = Person("Tom", 38); tom.display(); // Name: Tom Age: 38 Person li = Person("L", 100500); li.display(); // Name: Undefined Age: 18 }
Здесь в классе Person определен фабричный конструктор, который принимает два параметра:
factory Person(String name, int age) { if(name.length <2) { name = "Undefined"; } if(age <1 || age > 110) { age = 18; } return Person._create(name, age); }
При создании объекта могут быть переданы некорректные значения. В данном случае будем считать, что если длина переданного имени меньше 2 символов, то для переменной name в качестве значения устанавливаем строку "Undefined". Аналогично если передан недействительный возраст - меньше 1 или больше 110, то устанавливаем некоторое значение по умолчанию.
Для собственно создания объекта Person вызываем приватный именнованный конструктор Person._create()
Особенно полезен фабричный конструктор при реализации синглтонов - объектов, которые должен существовать в едином виде. А сам синглтон также выступает как паттерн проектирования, который гарантирует, что в программе может существовать только один объект данного типа. Такие объекты находят широкое применение в различных сферах, например, подключение к базе данных, которое нередко реализуется как синглтон, и другие.
Общая форма синглтона в языке Dart может быть описана следующим образом:
class Singleton { Singleton._(); static Singleton _instance = Singleton._(); factory Singleton() => _instance; }
В классе есть приватный конструктор, который можно вызвать только внутри класса. Для хранения единственного объекта класса определена приватная статическая константа, которая создается с помощью приватного конструктра. И есть фабричный конструктор, который возвращает значение этой статической переменной.
Например, в приложении одномоментно может быть залогинен только один пользователь. Для представления функциональности пользователя определим в файле user.dart следующий класс User:
class User{ String name = ""; // логин для входа в приложение bool isLogged = false; // залогинен ли пользователь static final User user = User._(); User._(); factory User.login(String name) { if(!user.isLogged){ // если пользователь не залогинен user.name = name; // устанавливаем логи user.isLogged = true; // указываем, что пользователь вошел в приложение print("Пользователь ${user.name} вошел в приложение"); } else{ // в фабричных конструкторах нельзя обращаться к this // print("С приложением уже работает ${this.name}"); print("С приложением уже работает ${user.name}"); } return user; } void info() { if(user.isLogged) print("Текущий пользователь ${user.name}"); } // выход из приложения void logout(){ print("${user.name} вышел из приложения"); user.isLogged = false; } }
Для хранения логина (имени пользователя) класс определяет переменную name. А для установки, залогинен ли пользователь, определена переменная isLogged, которая по умолчанию равна false.
Поскольку только один пользователь может существовать в системе, то для представления этого пользователя определено статическое поле user
static final User user = User._();
Поскольку не планируется менять значение этого поля, то оно определено как константа.
Для создания объекта User применяется приватный конструктор User._()
, который доступен только в текущем файле класса.
И также в классе определен именнованный фабричный конструктор User.login()
:
factory User.login(String name) { if(!user.isLogged){ // если пользователь не залогинен user.name = name; // устанавливаем логи user.isLogged = true; // указываем, что пользователь вошел в приложение print("Пользователь ${user.name} вошел в приложение"); } else{ // в фабричных конструкторах нельзя обращаться к this // print("С приложением уже работает ${this.name}"); print("С приложением уже работает ${user.name}"); } return user; }
Фабричный конструктор, как и обычные конструкторы, может принимать параметры. В данном случае передаем в конструктор для установки name.
В самом конструкторе проверяем, залогинен ли пользователь, то есть равна ли truen переменная isLogged. Если она равна false, то создаем изменяем значения name и isLogged. Если же пользователь уже ранее вошел в приложение, то просто выводим некоторую информацию. И в конце возвращаем созданный объект User.
То есть фабричный конструктор возвращает объект User из приватной статической константы. То есть мы получаем объект, который существует в единственном числе.
Также обратите внимание, что в фабричных конструкторах нельзя обращаться к текущему объекту через ключевое слово this:
// print("С приложением уже работает ${this.name}");
В функции main определяем две переменных это класса:
import 'user.dart'; // подключаем класс User void main (){ User tom = User.login("Tom"); User bob = User.login("Bob"); bob.info(); }
Консольный вывод:
Пользователь Tom вошел в приложение С приложением уже работает Tom Текущий пользователь Tom
По консольному выводу мы видим, что обе переменных - tom и bob указывают на один и тот же объект User. И чтобы второй пользователь мог войти приложение, надо, чтобы первый пользователь выше из приложения, вызвав метод logout:
import 'user.dart'; // подключаем класс User void main (){ User tom = User.login("Tom"); tom.info(); tom.logout(); User bob = User.login("Bob"); bob.info(); }
Где может применяться подобная техника? Прежде всего это различные варианты кэширования, когда вместо того, чтобы тратить дополнительные ресурсы на создание нового объекта, мы можем взять из кэша уже существующий. А если он отсутствует, то создать его.