Изучив классы фреймворка, пакеты сторонних разработчиков или документацию Dart / Flutter, вы, натыкались на ключевое слово factory
и задавались вопросом, что это значит.
В этой статье мы собираемся прояснить:
Значение ключевого слова
Когда вам следует его использовать
Разница между
factory
и порождающим конструкторомРазличия между
factory
иstatic
Фабричный метод
Прежде чем мы углубимся в синтаксис и семантику ключевого слова factory
, давайте рассмотрим его происхождение.
Ключевое слово factory
в Dart на самом деле является синтаксическим сахаром для выражения чего-то. Это базовый паттерн, который называется фабричным методом или шаблоном фабричного метода.
По сути, конструктор по умолчанию (тот, который вы вызываете с помощью Cat()
) — это не что иное, как static
метод, определенный в классе ( Cat
), возвращаемый тип которого должен быть того же типа ( Cat
). Основным отличием по сравнению с «нормальной» static
функцией в классе является невозможность изменения его возвращаемого типа.
Основные преимущества использования паттерна проектирования "фабрика" следующие:
Ответственность за создание объектов лежит не внутри самого класса, а в отдельном классе (фабричный классе), который реализует интерфейс.
Создание объекта лишено гибкости, поскольку вы не можете изменить конкретный экземпляр объекта.
Другими словами:
Breeder
не нужно знать, как создать экземплярCat
, потому чтоCat
производится на фабриках ????????.Breeder
только говоритmakeACat()
, и фабрика возвращает новогоCat
.
В этом и преимущество, что Breeder
не меняет своего поведения, если меняется способ производства Cat
.
Вывод: Его можно использовать для создания объектов, не раскрывая вызывающей стороне детали базовой реализации.
Типы конструкторов
В Dart есть порождающие и фабричные конструкторы, которые могут быть именованные или неименованные.
Примером порождающего конструктора является конструктор по умолчанию, который создается автоматически для каждого класса.
Давайте рассмотрим класс, чтобы лучше понять различные типы конструкторов:
class Cat {
final Color color;
}
В нашем примере существует класс Cat
только с одним свойством: color
типа Color
.
Типы конструкторов будут выглядеть следующим образом:
Генеративный |
Фабричный |
|
неименованный |
|
|
именованный |
|
|
Имейте в виду, что вам не разрешается создавать конструктор
factory
с именем, подобным уже существующему конструктору - либо порождающему, либо фабричному, и не имеет значения, именованный или неименованный.Единственным исключением для этого является случай, когда вы определяете неименованный
factory
конструктор, явно не определив неименованный порождающий конструктор. Когда вы не определяете неименованный конструктор, он будет сгенерирован для вас автоматически, а когда вы определитеfactory
конструктор, он будет переопределен.
Ключевое слово в Dart
Ключевое слово factory
не является точной реализацией 1:1 того, как в классических языков ООП, таких как C++ или Java.
Идея заключается в том, чтобы иметь отдельный класс, который обрабатывает создание объекта (например, CatFactory
????).
Однако, используя factory
конструктор, вы по-прежнему сохраняете логику создания объекта внутри того же класса. За исключением создания экземпляров подклассов, что также возможно с помощью factory
конструкторов.
Когда вам следует его использовать
"Используйте ключевое слово
factory
при реализации конструктора, который не всегда создает новый экземпляр своего класса. Например, конструкторfactory
может возвращать экземпляр из кэша или экземпляр подтипа. Другим вариантом использования фабричных конструкторов является инициализация конечной переменной с использованием логики, которая не может быть обработана в списке инициализаторов."
В документации в основном упоминаются три варианта использования:
Возврат экземпляра из кэша
Возвращает экземпляр подтипа
Инициализация конечной переменной
Давайте объясним пример документов один за другим.
Экземпляр из кэша
Давайте представим, что у нас есть ColorComputer
, которому требуется очень много времени, чтобы вычислить цвет кошки. У нас также есть CatCache
, в котором хранится последний созданный цвет кошки, чтобы избежать необходимости выполнять heavyColorComputation()
каждый раз, когда создается экземпляр Cat.colored
.
class Cat {
Cat(this.color);
factory Cat.colored(CatCache catCache) {
Cat? cachedCat = catCache.getCachedColorCat();
if (cachedCat != null) {
return cachedCat;
}
Color color = ColorComputer().heavyColorComputation();
return Cat(color);
}
final Color color;
}
В этом случае можно использовать конструктор factory
, т.к. мы вернем существующий экземпляр Cat
(если кэш попадет).
Сложная инициализация final переменной
Если у вас есть более сложная инициализация final
переменной, которая не может быть обработана внутри списка инициализаторов. Для решения можно использовать factory
конструктор.
class Cat {
Cat._({
required this.id,
required this.name,
required this.age,
required this.color
});
final int id;
final String name;
final int age;
final Color color;
factory Cat.fromJson(Map<String, dynamic> json) {
DateTime now = DateTime.now();
late Color color;
if (now.hour < 12) {
color = const Color(0xFF000000);
}
else {
color = const Color(0xFFFFFFFF);
}
return Cat._(
id: json['id'],
name: json['name'],
age: json['age'],
color: color,
);
}
void meow() {
print('Meow!');
}
void whoAmI() {
print('I am $name ($id) and I am $age years old. My color is $color.');
}
}
Здесь инициализация переменной color
требует некоторой логики. Поскольку необходимо выполнить несколько инструкций, это лучше сделать внутри конструктора factory
.
Давайте вызовем конструктор fromJson
и проверим его выходные данные:
const String myJson = '{"id": 5, "name": "Herbert", "age": 7}';
final Cat decodedCat = Cat.fromJson(jsonDecode(myJson));
decodedCat.meow();
decodedCat.whoAmI();
Это приводит к следующему результату:
"Meow!
I am Herbert (5) and I am 7 years old. My color is Color(0xffffffff)."— decodedCat
Экземпляр подтипа
Другим вариантом использования конструктора factory
является возврат экземпляра производного класса. Это невозможно с помощью порождающего конструктора.
Это может быть полезно, если логика принятия решения о том, какой подкласс возвращать, всегда одинакова во всем вашем приложении. Вместо того чтобы дублировать его, вы могли бы реализовать его в централизованном месте.
abstract class Cat {
Cat({required this.age});
int age;
factory Cat.makeCat(bool aggressive, int age) {
if (aggressive || age < 3) {
return AggressiveCat(age: age);
}
return DefensiveCat(age: age);
}
void fight();
}
class AggressiveCat extends Cat {
AggressiveCat({required super.age});
@override
void fight() {
print('Where dem enemies at?!');
}
}
class DefensiveCat extends Cat {
DefensiveCat({required super.age});
@override
void fight() {
print('Nah, I\'m staying!');
}
}
Разница между порождающим конструктором и factory конструктором
Что мы узнали: порождающий конструктор всегда возвращает новый экземпляр класса, именно поэтому ему не нужно ключевое слово return
.
С другой стороны, factory
конструктор связан с гораздо более слабыми ограничениями. Для конструктора factory
достаточно, чтобы возвращаемый класс, имел тот же тип, что и сам класс, или он удовлетворяет его интерфейсу (например, подклассу). Это может быть новый экземпляр класса, но также может быть существующий экземпляр класса (как показано в примере кэша выше).
Фабрика может использовать поток управления, чтобы определить, какой объект возвращать, и поэтому должна использовать ключевое слово return
. Для того чтобы фабрика вернула новый экземпляр класса, она должна сначала вызвать порождающий конструктор.
Все незначительные и основные различия:
Фабричные конструкторы могут вызывать другой конструктор (и должны вызывать, если он не возвращает существующий экземпляр)
Фабричные конструкторы не могут использовать список инициализаторов (поскольку они напрямую не создают новый экземпляр)
Фабричным конструкторам в отличие от порождающего конструктора разрешено возвращать существующий объект
Фабричным конструкторам разрешено возвращать подкласс
Фабричным конструкторам не нужно инициализировать переменные экземпляра класса
-
Производный класс не может вызывать фабричный конструктор суперкласса. Как следствие, класс, предоставляющий исключительно фабричные конструкторы, не может быть расширен.
В противном случае компилятор будет жаловаться: “Ожидается порождающий конструктор, но была найдена фабрика”.
Порождающие конструкторы не могут устанавливать
final
cвойства в теле конструктораПорождающие конструкторы могут быть
const
и не нуждаются в перенаправлении
Разница между static и factory
Вы можете спросить себя: “Но зачем мне нужно это ключевое слово? Разве я не могу просто использовать обычные статические методы?!”.
На самом деле, нет большой разницы между static
методом и factory
конструктором. Хотя синтаксис немного отличается.
Вообще говоря, static
метод имеет более слабые ограничения, но также и меньше синтаксического сахара. Это потому, что каждый factory
конструктор является (технически) static
методом, но не каждый static
метод является factory
конструктором. Таким образом, если вы определяете factory
конструктор, компилятор знает о ваших намерениях и может поддержать вас. Самое большое различие, заключается в том, что возвращаемый тип фабричного конструктора устанавливается для текущего класса или производных классов, в то время как для статического метода вы можете указать любой возвращаемый тип.
Если мы воспользуемся одним из приведенных выше примеров, то увидим, что мы можем достичь того же результата с помощью static
метода:
class Cat {
Cat._({
required this.id,
required this.name,
required this.age,
required this.color
});
final int id;
final String name;
final int age;
final Color color;
static Cat catfromJson(Map<String, dynamic> json) {
DateTime now = DateTime.now();
late Color color;
if (now.hour < 12) {
color = const Color(0xFF000000);
}
else {
color = const Color(0xFFFFFFFF);
}
return Cat._(
id: json['id'],
name: json['name'],
age: json['age'],
color: color,
);
}
factory Cat.fromJson(Map<String, dynamic> json) {
DateTime now = DateTime.now();
late Color color;
if (now.hour < 12) {
color = const Color(0xFF000000);
}
else {
color = const Color(0xFFFFFFFF);
}
return Cat._(
id: json['id'],
name: json['name'],
age: json['age'],
color: color,
);
}
void meow() {
print('Meow');
}
void whoAmI() {
print('I am $name ($id) and I am $age years old. My color is $color.');
}
}
С точки зрения удобства читаемости кода, хорошей практикой является использование factory
конструктора вместо static
методов. Это делает создание объекта более очевидным.
Чтобы дать вам полный обзор, я перечислил все различия:
factory
конструктор в отличие отstatic
метода может возвращать только экземпляр текущего класса или подклассовstatic
метод может бытьasync
. Посколькуfactory
конструкторам необходимо возвращать экземпляр текущего или подкласса, он не может вернутьFuture
.static
метод не может быть неименованным, в то время какfactory
конструкторы могутЕсли вы укажете именованный
factory
конструктор, конструктор по умолчанию будет автоматически удаленФабричные конструкторы могут использовать специальный синтаксис для перенаправления
Для фабричного конструктора не обязательно указывать общие параметры
Фабричные конструкторы могут быть объявлены
const
Фабричный конструктор не может возвращать тип, допускающий значение null.
При создании документации
dartdoc
,factory
конструкторы будут перечислены в разделе “Конструкторы”.static
метод будет найден в другом месте в нижней части документации
Таким образом, подвох кроется в деталях, но с точки зрения низкого уровня не имеет значения, используете ли вы static
метод или factory
конструктор.
Вывод
Ключевое слово factory
может быть полезно, если создание экземпляра класса превышает определенную сложность. Это может иметь место при кэшировании или при использовании иной сложной логики.
Кроме того, ничто не противоречит использованию static
метода. Хотя factory
конструкторы предназначены именно для создания экземпляра, в то время как static
метод имеет гораздо более широкую область применения.
В конечном счете, все сводится к личным предпочтениям.
PackRuble
Поток мыслей в оригинальной статье порядком сумбурный, однако это не отменяет практической значимости статьи :) Пишите/переводите ещё, материал интересный ????