Irbisa · Шпаргалка

ООП в Dart

Классы, наследование, интерфейсы, примеси и абстрактные классы

Junior-разработчик · 22 вопроса

  1. 01

    Какие четыре принципа ООП существуют и как каждый из них выглядит в Dart?

    Инкапсуляция прячет состояние за публичным API, наследование переиспользует код родителя, полиморфизм позволяет одному вызову выполнять разные реализации, а абстракция показывает, что объект делает, но не как.

  2. 02

    Как работают конструкторы в Dart (обычные, именованные и списки инициализации) и почему final-поля нужно задать до тела конструктора?

    Обычный (generative) конструктор создаёт новый экземпляр; именованный, например Point.origin(), даёт дополнительный способ создать тот же класс с понятным названием.

    class Point {  final double x;  final double y;   // generative: fields set straight from parameters  Point(this.x, this.y);…
  3. 03

    Что такое factory-конструктор, чем он отличается от обычного и может ли он вернуть null или закешированный экземпляр?

    factory-конструктор не обязан создавать новый объект: его тело само решает, что вернуть (закешированный экземпляр, синглтон или подтип).

    class Logger {  Logger._internal(this.name);  final String name;   static final _cache = <String, Logger>{};…
  4. 04

    Что делает super.key в конструкторе виджета и когда super-параметры использовать нельзя?

    super.key является super-параметром: конструктор принимает key и передаёт его в конструктор суперкласса.

    class Avatar extends StatelessWidget {  // before: const Avatar({Key? key, required this.url}) : super(key: key);  const Avatar({super.key, required this.url, this.size = 40});   final String url;  final double size;…
  5. 05

    Поддерживает ли Dart перегрузку методов и как добиться того же эффекта?

    Нет: в классе не может быть двух методов или конструкторов с одним именем и разными параметрами.

    class Toast {  // one method instead of show(text), show(text, duration), show(text, duration, action)  static void show(    String text, {    Duration duration = const Duration(seconds: 2),    String? actionLabel,…
  6. 06

    В Dart нет ключевого слова private. Как работает приватность через _ и что означает конструктор ._()?

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

    // cart.dartclass Cart {  Cart._(); // private: other files cannot call it  static final instance = Cart._();   final _items = <String>[]; // private to cart.dart…
  7. 07

    Как работают геттеры и сеттеры в Dart и почему публичное поле можно заменить геттером, не сломав вызывающий код?

    У каждого поля уже есть неявный геттер, а если оно не final, то и сеттер; get и set позволяют написать эти методы доступа вручную.

    class CartItem {  CartItem(this.price, this._quantity);  final double price;  int _quantity;   int get quantity => _quantity;…
  8. 08

    Когда член класса стоит делать static и чего статические члены делать не могут?

    static-член принадлежит самому классу, а не объекту: на всю программу он существует в одном экземпляре.

  9. 09

    В чём разница между extends, implements и with и какое место здесь занимает абстрактный класс?

    extends наследует реализацию родителя (родитель только один), implements берёт только контракт и заставляет написать каждый член заново, а with добавляет члены миксина.

    abstract class Shape {  double get area; // abstract: subclasses must provide it  String describe() => 'area ${area.toStringAsFixed(1)}'; // inherited} mixin Loggable {…
  10. 10

    Что такое миксин, когда его выбирают вместо наследования и что делает конструкция on?

    Миксин представляет собой переиспользуемый набор членов, который добавляется в класс через with, даже в несвязанные иерархии.

    mixin Validator {  // abstract: the host class must provide it  Map<String, String?> get fields;   bool get isValid => fields.values.every((v) => v != null && v.isNotEmpty);}…
  11. 11

    Что такое extension-методы, что они дают по сравнению с функциями-хелперами и когда они не срабатывают?

    Extension добавляет методы, геттеры, сеттеры или операторы к типу, который вам не принадлежит (String, DateTime, BuildContext), без наследования.

    extension StringCase on String {  String capitalize() =>      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';} extension ContextX on BuildContext {…
  12. 12

    Как работает перегрузка операторов в Dart и какие операторы можно перегрузить?

    Вы объявляете метод экземпляра с ключевым словом operator, Money operator +(Money other), и дальше a + b вызывает его.

    final class Money {  const Money(this.cents);  final int cents;   Money operator +(Money other) => Money(cents + other.cents);  Money operator *(int times) => Money(cents * times);…
  13. 13

    Этот Set должен был убрать дубликат, но печатает 2. Чего не хватает и что будет, если изменить объект, который уже лежит в множестве?

    class Point {  Point(this.x, this.y);  int x, y;   @override  bool operator ==(Object other) =>      other is Point && other.x == x && other.y == y;} void main() {  final points = {Point(1, 2), Point(1, 2)};  print(points.length);}

    Не хватает hashCode: Set и Map сначала сравнивают хеш-коды и вызывают == только при совпадении, а у двух отдельных объектов identity-хеши разные.

  14. 14

    Что такое callable-класс (класс с методом call()) и когда его стоит писать?

    Класс с методом call можно вызывать как функцию: getUserName('42') выполняет getUserName.call('42').

    abstract interface class UserRepository {  Future<String> nameOf(String id);} class GetUserName {  GetUserName(this._repo);…
  15. 15

    Почему виджеты и модели во Flutter неизменяемые и как обеспечить неизменяемость в Dart (final, @immutable, copyWith)?

    Flutter замечает изменение, сравнивая старый объект с новым; если изменить объект на месте, это один и тот же объект, и ValueNotifier, BLoC, updateShouldNotify и select в Riverpod ничего не заметят.

    @immutableclass Profile {  Profile({required this.name, this.email, List<String> tags = const []})      : tags = List.unmodifiable(tags);   final String name;…
  16. 16

    Что такое const-конструктор и почему Flutter предпочитает const-виджеты?

    const-конструктор позволяет создать объект на этапе компиляции; одинаковые const-выражения канонизируются в один общий экземпляр, поэтому const Text('Hi') остаётся одним и тем же объектом при каждом build.

    class Point {  const Point(this.x, this.y); // all fields final, no body  final int x, y;} void main() {…
  17. 17

    Что контролируют модификаторы классов Dart 3 base, interface, final, sealed и mixin class?

    Они ограничивают, что другие библиотеки могут делать с классом: base разрешает только наследование, interface разрешает только реализацию, final не разрешает ни того ни другого, sealed фиксирует набор подтипов, чтобы switch мог быть исчерпывающим, а mixin class позволяет использовать класс ещё и после with.

    // interface: others implement it, nobody inherits your bodiesabstract interface class UserRepository {  Future<String> nameOf(String id);} // base: others may extend, never implement, so _send always runs…
  18. 18

    Почему Flutter предпочитает композицию наследованию и как бы вы отрефакторили раздутый базовый класс BaseScreen?

    Наследование привязывает подкласс к внутренностям и порядку вызовов базового класса, поэтому одно изменение в базе может сломать подклассы, которые вы не трогали (проблема хрупкого базового класса).

    // before: class ProfileScreen extends BaseScreen { /* six overridden hooks */ } // after: the shared part is a widget the screens composeclass AsyncView<T> extends StatelessWidget {  const AsyncView({super.key, required this.snapshot, required this.builder});…
  19. 19

    Найдите баг: в интеграционном тесте ApiClient(baseUrl: server.url) всё равно отправляет запросы в продовый API.

    class ApiClient {  ApiClient._(this.baseUrl);  final String baseUrl;   static ApiClient? _instance;   factory ApiClient({required String baseUrl}) =>      _instance ??= ApiClient._(baseUrl);   Uri endpoint(String path) => Uri.parse('$baseUrl$path');}

    Фабрика кеширует первый экземпляр в статическом поле и молча игнорирует baseUrl при всех следующих вызовах, поэтому тот, кто первым создал ApiClient, фиксирует URL для всего изолята: тесты, флейворы и переключатель на стейджинг получают прод.

  20. 20

    Что напечатает этот код и почему замена with Caching, Logging на with Logging, Caching меняет результат?

    class Base {  String fetch() => 'base';} mixin Caching on Base {  @override  String fetch() => 'cache(${super.fetch()})';}mixin Logging on Base {  @override  String fetch() => 'log(${super.fetch()})';} class Repo extends Base with Caching, Logging {} void main() => print(Repo().fetch());

    Он напечатает log(cache(base)).

  21. 21

    Вызов Screen('home') бросает LateInitializationError. В каком порядке Dart создаёт объект и как это исправить?

    class Base {  Base() { onInit(); }  void onInit() {}} class Screen extends Base {  Screen(this.title) : upper = title.toUpperCase() {    controller = 'controller for $title';  }  final String title, upper;  late final String controller;   @override  void onInit() => print('$title $upper $controller');}

    Когда выполняется тело конструктора Base, Screen собран только наполовину.

  22. 22

    Найдите баг: cart.contains(item) для одного и того же товара возвращает то true, то false в зависимости от того, лежит в корзине обычная версия или версия со скидкой.

    class Product {  Product(this.id);  final String id;  @override  bool operator ==(Object other) => other is Product && other.id == id;  @override  int get hashCode => id.hashCode;}class SaleProduct extends Product {  SaleProduct(super.id, this.discount);  final int discount;  @override  bool operator ==(Object other) =>      other is SaleProduct && other.id == id && other.discount == discount;  @override  int get hashCode => Object.hash(id, discount);}

    Product.== проверяет other is Product, а этому условию удовлетворяет и любой SaleProduct, поэтому обычный товар равен своей версии со скидкой только в одну сторону.