Что такое абстрактный класс в Dart и для чего он используется?

Ответ

В Dart абстрактный класс — это класс, который нельзя инстанциировать напрямую с помощью конструктора. Он служит шаблоном или контрактом для своих подклассов, определяя общий интерфейс (методы, свойства) и, возможно, часть реализации.

Ключевые характеристики:

  • Объявляется с ключевым словом abstract.
  • Может содержать абстрактные методы (объявлены без тела, с ;).
  • Может содержать конкретные методы (с полной реализацией).
  • Может содержать поля, геттеры, сеттеры и конструкторы (хотя конструктор абстрактного класса можно вызвать только из конструктора подкласса).
  • Класс, наследующий абстрактный класс, должен реализовать (override) все его абстрактные методы, если сам не объявлен абстрактным.

Пример: Моделирование геометрических фигур

abstract class Shape {
  // Абстрактный метод: должен быть реализован в подклассе
  double area(); 

  // Абстрактный геттер
  String get name;

  // Конкретный метод с реализацией, доступный всем подклассам
  void printDescription() {
    print('I am a $name with area: ${area().toStringAsFixed(2)}');
  }
}

class Circle extends Shape {
  final double radius;

  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;

  @override
  String get name => 'Circle';
}

class Rectangle extends Shape {
  final double width;
  final double height;

  Rectangle(this.width, this.height);

  @override
  double area() => width * height;

  @override
  String get name => 'Rectangle';
}

void main() {
  final shapes = <Shape>[
    Circle(5),
    Rectangle(4, 6),
  ];

  for (final shape in shapes) {
    shape.printDescription(); // Полиморфный вызов
    // Вывод:
    // I am a Circle with area: 78.54
    // I am a Rectangle with area: 24.00
  }
}

Сравнение с интерфейсами (implicit interfaces) в Dart: В Dart каждый класс неявно определяет интерфейс. Абстрактный класс, не содержащий конкретных методов, концептуально похож на интерфейс. Однако ключевое отличие в том, что абстрактный класс может содержать реализацию и состояние (поля), а также ограничивать иерархию наследования (класс может наследовать только один абстрактный класс, но реализовать много интерфейсов). Я использую абстрактные классы, когда хочу задать общее поведение и частичную реализацию для группы связанных классов.

Ответ 18+ 🔞

Э, сабака сука, ну слушай сюда, разберёмся с этими абстрактными классами в Dart. Представь себе, что это как чертёж от инженера, который сам по себе — просто бумажка, ёпта. Ты не можешь построить дом из чертежа, но все строители, которые по нему работают, обязаны сделать окна, двери и крышу именно так, как там нарисовано. Вот и абстрактный класс — это такой чертёж для других классов.

Что это за зверь такой:

  • Объявляешь его с волшебным словом abstract. Без него — ни хуя себе, не сработает.
  • Может содержать абстрактные методы — это когда метод объявлен, но у него вместо тела точка с запятой. Как будто крикнул подмастерьям: "Сделайте хуйню!", но как именно — не сказал. Пусть сами думают.
  • Но может и конкретные методы иметь — уже с готовой реализацией, чтобы подклассы не парились и могли пользоваться.
  • Поля, геттеры, сеттеры, конструкторы — всё может быть. Но конструктор этого абстрактного чудака можно вызвать только из конструктора класса-наследника, иначе доверия ебать ноль.

Короче, пример на пальцах: Геометрические фигуры

Представь, ты начальник цеха по производству фигур. Ты говоришь: "Все фигуры должны уметь считать свою площадь и иметь имя. А как именно считать — это ваши проблемы, мартышлюшки".

abstract class Shape {
  // Абстрактный метод: объявил и послал всех на хуй. Пусть наследники реализуют.
  double area(); 

  // Абстрактный геттер. Без имени — никуда.
  String get name;

  // А вот это уже конкретный метод. Готовый инструмент. Наследники могут просто пользоваться.
  void printDescription() {
    print('Я $name, и моя площадь: ${area().toStringAsFixed(2)}');
  }
}

// Приходит первый работяга — Круг.
class Circle extends Shape {
  final double radius;

  Circle(this.radius);

  @override // Говорим: "Батя, я сделал по-своему, но как ты велел!"
  double area() => 3.14159 * radius * radius; // Формулу, блядь, ещё в школе учил.

  @override
  String get name => 'Круг';
}

// Второй работяга — Прямоугольник.
class Rectangle extends Shape {
  final double width;
  final double height;

  Rectangle(this.width, this.height);

  @override
  double area() => width * height; // Ширина на высоту, ебать копать, что может быть проще?

  @override
  String get name => 'Прямоугольник';
}

void main() {
  // Создаём коллекцию фигур. Главное, что они все — Shape. Нам похуй на конкретный тип.
  final shapes = <Shape>[
    Circle(5),
    Rectangle(4, 6),
  ];

  for (final shape in shapes) {
    shape.printDescription(); // Магия полиморфизма! Вызовется своя реализация area() для каждого.
    // Вывод:
    // Я Круг, и моя площадь: 78.54
    // Я Прямоугольник, и моя площадь: 24.00
  }
}

А в чём отличие от интерфейсов, спросишь ты? В Dart каждый класс неявно определяет интерфейс, это да. Если абстрактный класс состоит только из абстрактных методов, то он, в общем-то, и есть интерфейс. Но фишка в том, что абстрактный класс может быть хитрой жопой: он может уже часть работы сделать за тебя (конкретные методы) и хранить какое-то состояние (поля). И ещё: наследоваться можно только от одного такого класса-родителя, а интерфейсов реализовать — овердохуища.

Итог: Использую абстрактный класс, когда у меня есть группа очень похожих сущностей, у которых общая не только "форма" (методы), но и какая-то базовая "начинка" (реализация). Когда хочу сказать: "Вот вам основа, дебилы, остальное доделывайте сами, но строго по моим правилам".