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

Последнее обновление: 04.04.2023

По умолчанию конструкторы класса создают и возвращают новый объект этого класса. Но, возможно, не всегда потребуется создавать новый объект класса. Возможно, мы захотим использовать и возвращать из конструктора уже имеющийся объект. Для этой цели в языке 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();
}

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850