Наследование, миксины и абстрактные классы

Наследование позволяет строить новые классы на основе существующих, не переписывая общий код заново.

Суть: extends наследует поведение, @override меняет его, абстрактные классы задают контракт без реализации, а миксины (with) добавляют возможности нескольким классам сразу.

Полиморфизм — третий кит ООП после инкапсуляции и наследования — заслуживает отдельного слова. Он означает, что переменная типа Animal может хранить любого потомка, и вызов animal.sound() выберет нужную реализацию автоматически, в зависимости от реального типа объекта. Благодаря этому вы пишете код, работающий с абстракцией, а конкретику подставляет среда выполнения. Это основа того, почему Flutter оперирует типом Widget, а на экране оказываются ваши конкретные виджеты.

Если у вас есть классы Cat и Dog, оба умеют дышать, есть и спать. Дублировать этот код в каждом — расточительно. Наследование выносит общее в родительский класс Animal, а потомки добавляют только своё. Так строится вся иерархия виджетов Flutter: StatelessWidget и StatefulWidget наследуют от общего Widget.

class Animal {
  final String name;
  Animal(this.name);

  String sound() => '...';
  void describe() => print('$name говорит ${sound()}');
}

class Dog extends Animal {
  Dog(String name) : super(name);   // вызов конструктора родителя

  @override
  String sound() => 'Гав';          // переопределяем поведение
}

void main() {
  Dog('Рекс').describe();           // Рекс говорит Гав
}

Аннотация @override сообщает, что метод переопределяет родительский. super обращается к родителю — например, чтобы вызвать его конструктор или метод.

Как работают абстракции и миксины под капотом

Абстрактный класс (abstract class) нельзя создать напрямую — он лишь задаёт контракт: «у всех потомков должен быть метод sound()», но как именно — решает потомок. Миксин (mixin ... with) — это горизонтальное переиспользование: набор методов, который можно «подмешать» к любому классу, даже не родственному по иерархии.

            Animal (абстрактный контракт)
           /      \
        Dog        Cat       наследование (extends): "является"
         |
      + with Swimmer         миксин: "умеет плавать"
         |
   класс Dog получил метод swim() от миксина,
   не наследуя весь класс Swimmer
abstract class Shape {
  double area();          // контракт без тела
}

class Circle extends Shape {
  final double r;
  Circle(this.r);
  @override
  double area() => 3.14159 * r * r;
}

mixin Swimmer {
  void swim() => print('Плыву');
}

class Duck extends Animal with Swimmer {
  Duck(String name) : super(name);
  @override
  String sound() => 'Кря';
}
# Аналог наследования и переопределения на Python
class Animal:
    def __init__(self, name):
        self.name = name
    def sound(self):
        return '...'
    def describe(self):
        print(f'{self.name} говорит {self.sound()}')

class Dog(Animal):
    def sound(self):          # override
        return 'Гав'

class Cat(Animal):
    def sound(self):
        return 'Мяу'

for a in [Dog('Рекс'), Cat('Мурка')]:
    a.describe()              # каждый звучит по-своему — полиморфизм

Частые ошибки

  • Забыть @override при переопределении — код часто работает, но теряется проверка совпадения сигнатур.
  • Пытаться создать объект абстрактного класса — это запрещено, абстракция только для наследования.
  • Глубокие иерархии наследования. Пять уровней extends — путь к хрупкому коду; чаще лучше композиция или миксины.

Best practices

  • Предпочитайте композицию наследованию: Flutter строит интерфейс из вложенных виджетов, а не из длинных цепочек extends.
  • Используйте миксины для общих возможностей (логирование, плавание) без жёсткой иерархии.
  • Абстрактные классы задавайте, когда нужен единый контракт для группы классов.

Dart 3 добавил тонкий контроль над наследованием через модификаторы классов: final запрещает наследование вовсе, base требует наследования без реализации интерфейса, interface наоборот. Эти инструменты нужны авторам библиотек, чтобы задать чёткие правила использования своих классов. Новичку их знать не обязательно, но полезно понимать: если компилятор жалуется на запрещённое наследование, скорее всего, автор класса сознательно его закрыл.

Итог: наследование, абстракции и миксины — это инструменты переиспользования. Во Flutter вы будете больше композировать виджеты, чем наследоваться, но понимание иерархии классов необходимо, чтобы читать исходники фреймворка.

Проверьте себя
1. Что делает ключевое слово super в конструкторе потомка?
AСоздаёт новый объект
BВызывает конструктор или метод родительского класса
CУдаляет родителя
DДелает класс абстрактным
2. Чем миксин (mixin ... with) отличается от обычного наследования?
AНичем
BМиксин добавляет возможности классам горизонтально, не выстраивая иерархию «является»
CМиксин нельзя переопределять
DМиксин работает только с числами