Что такое перегрузка конструкторов и как она реализуется в Dart?

Ответ

Перегрузка конструкторов — это возможность языка определять несколько конструкторов с одним именем, но разными параметрами. Dart не поддерживает перегрузку методов или конструкторов в классическом понимании (как Java или C#).

Вместо этого Dart предлагает несколько гибких механизмов для достижения той же цели — создания объектов разными способами:

  1. Именованные конструкторы: Основной способ. Позволяют дать конструктору явное имя для описания его цели.

    class Point {
      double x, y;
    
      Point(this.x, this.y); // Основной конструктор
    
      Point.origin() : this(0, 0); // Именованный конструктор
    
      Point.fromJson(Map<String, double> json)
          : x = json['x']!,
            y = json['y']!;
    }
    
    var p1 = Point(5, 10);
    var p2 = Point.origin();
    var p3 = Point.fromJson({'x': 1.0, 'y': 2.0});
  2. Необязательные параметры с значениями по умолчанию: Позволяют одному конструктору покрывать множество случаев.

    • Именованные параметры ({}): Удобны, когда нужно указать только некоторые параметры.
    • Позиционные параметры ([]): Редко используются для конструкторов.

      
      class Rectangle {
      int width;
      int height;
      Color color;
      
      // Один конструктор с необязательными именованными параметрами
      Rectangle({
      this.width = 100,
      this.height = 100,
      this.color = Colors.blue,
      });
      }

    var rect1 = Rectangle(); // 100x100, синий var rect2 = Rectangle(width: 200, color: Colors.red); // 200x100, красный

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

    class Logger {
      final String name;
      static final Map<String, Logger> _cache = <String, Logger>{};
    
      factory Logger(String name) {
        return _cache.putIfAbsent(name, () => Logger._internal(name));
      }
    
      Logger._internal(this.name); // Приватный конструктор
    }
    
    var logger1 = Logger('app');
    var logger2 = Logger('app'); // Вернёт тот же экземпляр из кэша

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

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сидишь такой, переползаешь с Java или C#, и тут — бац! — а в Dart перегрузки конструкторов, как ты привык, нихуя нет. Совсем. Никаких там Point(int x), Point(double x), Point(int x, int y) с одним именем. Ядрёна вошь, думаешь, приехали.

Но не спеши материться, чувак. Дарт — он хитрая жопа. Он просто подходит к вопросу с другой стороны, и, если честно, часто это даже удобнее получается. Смотри, какие у него фокусы в рукаве.

1. Именованные конструкторы — наш главный козырь

Вот это, бля, основа основ. Не перегружать один метод, а дать каждому способу создания своё говорящее имя. Красота же!

class Point {
  double x, y;

  Point(this.x, this.y); // Ну, стандартный, понятно

  // А это что за зверь? Именованный, мать его!
  Point.origin() : this(0, 0); // Создаёт точку в начале координат. Сразу ясно, что происходит!

  Point.fromJson(Map<String, double> json) // А этот из JSON выковыривает данные
      : x = json['x']!,
        y = json['y']!;
}

// Используется — загляденье!
var p1 = Point(5, 10); // Классика
var p2 = Point.origin(); // О, сразу видно — ноль-ноль!
var p3 = Point.fromJson({'x': 1.0, 'y': 2.0}); // И тут всё прозрачно, ебать копать.

Чувствуешь разницу? Не надо гадать, какой из десяти Point() сработает. Видишь имя — понимаешь намерение. Удивление пиздец, как же это читаемо!

2. Один конструктор на все случаи жизни (с параметрами по умолчанию)

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

class Rectangle {
  int width;
  int height;
  Color color;

  // Смотри, один конструктор, но в нём — овердохуища возможностей!
  Rectangle({
    this.width = 100,   // Если не укажешь — будет 100
    this.height = 100,  // И тут 100
    this.color = Colors.blue, // И по умолчанию синий, красота!
  });
}

// И твори, что хочешь!
var rect1 = Rectangle(); // Без параметров? Да похуй! 100x100, синий.
var rect2 = Rectangle(width: 200, color: Colors.red); // А тут только ширину и цвет поменял. Идеально!

Именованные параметры ({}) — это просто песня. Указывай что нужно, остальное само подставится. Никакой путаницы с порядком аргументов, как в позиционной перегрузке. Волнение ебать, как же удобно!

3. Фабричный конструктор (factory) — для сложных делишек

А вот это уже магия по-взрослому. Когда нужно не просто поля присвоить, а какую-то хитрожопую логику выполнить при создании объекта.

class Logger {
  final String name;
  static final Map<String, Logger> _cache = <String, Logger>{}; // Кеш, сука!

  // Фабрика! Она может решить, что тебе вернуть.
  factory Logger(String name) {
    // Смотри: если объект с таким именем уже есть — вернёт старый. Нет — создаст новый.
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  Logger._internal(this.name); // А это приватный конструктор, чтоб с улицы не дёргали.
}

var logger1 = Logger('app'); // Создаст новый
var logger2 = Logger('app'); // А тут, бля, вернёт тот же самый экземпляр из кэша! Ни хуя себе!

factory — это мощь. Кеширование, возврат объектов подклассов, любая нестандартная инициализация. Пизда рулю! Хотя для асинхронной загрузки данных лучше статический метод использовать, но это уже другая история.

Короче, вывод какой? Да, в Dart нет той перегрузки, к которой ты, возможно, привык. Но то, что предлагают вместо неё — именованные конструкторы, параметры по умолчанию и фабрики — это часто даже лучше. Код становится понятнее, намерения видны как на ладони, и меньше шансов накосячить, передав аргументы не в том порядке. Привыкаешь быстро, а потом вообще удивляешься, как без этого жил.