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

«Что такое перегрузка конструкторов и как она реализуется в Dart?» — вопрос из категории ООП, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Перегрузка конструкторов — это возможность языка определять несколько конструкторов с одним именем, но разными параметрами. 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 часто оказываются даже более выразительными и удобными, чем классическая перегрузка.