← Назад к вопросам

Почему в Dart 2.12 появился abstract?

1.0 Junior🔥 111 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему в Dart 2.12 появился abstract?

В Dart 2.12 (выпущен в марте 2021) произошла революция: введена система null-safety и одновременно уточнены правила для абстрактных классов. Это было связано с более строгой типизацией языка.

История: До и после Dart 2.12

ДО Dart 2.12 (Dart 2.0 - 2.11)

// ✓ Работало, но было неоднозначно
abstract class Animal {
  void makeSound();  // Абстрактный метод
}

class Dog extends Animal {
  @override
  void makeSound() => print('Woof!');
}

// Также можно было создать класс с методом, который выглядит как абстрактный
// Но это была просто конвенция, а не правило
class Vehicle {
  void start() => throw UnimplementedError();
}

Проблема: Не было чёткого различия между классом, предназначенным для наследования, и обычным классом.

ПОСЛЕ Dart 2.12 (новые правила)

// ✓ Явный abstract класс
abstract class Animal {
  void makeSound();  // Обязательно переопределить
}

// ❌ Ошибка! Не можешь создать экземпляр
// final animal = Animal();  // Compilation error

// ✓ Правильно
class Dog extends Animal {
  @override
  void makeSound() => print('Woof!');
}

final dog = Dog();  // ✓ Ok

Преимущества abstract в Dart 2.12

1. Явная контрактация

// ✅ ЯСНО: это интерфейс/контракт для наследования
abstract class Repository {
  Future<List<User>> getUsers();
  Future<User?> getUserById(int id);
  Future<void> createUser(User user);
}

// Подклассы ОБЯЗАНЫ реализовать все методы
class UserRepository implements Repository {
  @override
  Future<List<User>> getUsers() async {
    // Реализация
    return [];
  }
  
  @override
  Future<User?> getUserById(int id) async {
    // Реализация
    return null;
  }
  
  @override
  Future<void> createUser(User user) async {
    // Реализация
  }
}

2. Невозможно ошибиться при создании

// ❌ Компиляторная ошибка
final repo = Repository();  // Can't instantiate abstract class

// ✓ Правильно
final repo = UserRepository();

3. Лучшая поддержка null-safety

// С введением null-safety (Dart 2.12)
abstract class Service {
  String getName();  // Non-nullable
  String? getDescription();  // Nullable
  
  // Частичная реализация
  void printInfo() {
    print('Service: ${getName()}');  // Всегда есть
    print('Description: ${getDescription()}');  // Может быть null
  }
}

// Подкласс должен учесть null-safety
class UserService extends Service {
  @override
  String getName() => 'UserService';  // Обязательно вернёт String
  
  @override
  String? getDescription() => null;  // Может быть null
}

4. Отличие от interface

// ✅ abstract class — может иметь реализацию
abstract class BaseService {
  void log(String message) {  // Реализация!
    print('[LOG] $message');
  }
  
  void handle(Request request);  // Абстрактный метод
}

// Подкласс может использовать готовый log()
class AuthService extends BaseService {
  @override
  void handle(Request request) {
    log('Handling auth request');
  }
}

// ✓ interface — только контракт (в Dart это просто class)
abstract class DatabaseAdapter {
  Future<void> connect();
  Future<void> disconnect();
  Future<List<Map>> query(String sql);
}

Практический пример: BLoC паттерн

// ✅ ХОРОШАЯ архитектура с abstract
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}

abstract class CounterState {}

class CounterInitial extends CounterState {}
class CounterUpdated extends CounterState {
  final int value;
  CounterUpdated(this.value);
}
class CounterError extends CounterState {
  final String message;
  CounterError(this.message);
}

// БЛОк с abstract event и state
abstract class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitial());
  
  Future<void> handleIncrement();
  Future<void> handleDecrement();
}

// Реализация
class CounterBlocImpl extends CounterBloc {
  int _count = 0;
  
  CounterBlocImpl() : super() {
    on<IncrementEvent>((event, emit) async {
      try {
        _count++;
        emit(CounterUpdated(_count));
      } catch (e) {
        emit(CounterError(e.toString()));
      }
    });
    
    on<DecrementEvent>((event, emit) async {
      try {
        _count--;
        emit(CounterUpdated(_count));
      } catch (e) {
        emit(CounterError(e.toString()));
      }
    });
  }
  
  @override
  Future<void> handleIncrement() async {
    add(IncrementEvent());
  }
  
  @override
  Future<void> handleDecrement() async {
    add(DecrementEvent());
  }
}

Сравнение: abstract vs обычный класс

// ❌ БЕЗ abstract (старый подход)
class Logger {
  void log(String message) => throw UnimplementedError();
}

// Проблема: ничто не запретит создать Logger()
final logger = Logger();  // Компилируется!
logger.log('test');  // Runtime error!

// ✅ С abstract (Dart 2.12+)
abstract class Logger {
  void log(String message);
}

// Правильная реализация
class ConsoleLogger extends Logger {
  @override
  void log(String message) => print(message);
}

// ✓ Работает правильно
final logger = ConsoleLogger();
logger.log('test');  // OK!

// ❌ Это не скомпилируется
// final badLogger = Logger();  // Compile error!

Когда использовать abstract

// ✅ Используй abstract когда:

// 1. Определяешь интерфейс для реализации
abstract class DataSource {
  Future<List<User>> getUsers();
  Future<void> saveUser(User user);
}

class RemoteDataSource implements DataSource { ... }
class LocalDataSource implements DataSource { ... }

// 2. Есть частичная реализация, которую наследники дополняют
abstract class BaseViewModel {
  void onStart() {}  // Может быть переопределён
  void onStop() {}   // Может быть переопределён
  void dispose() {   // Обязательна реализация
    throw UnimplementedError();
  }
}

// 3. Хочешь гарантировать определённые методы
abstract class Animal {
  String get name;
  void makeSound();
}

class Dog extends Animal {
  @override
  String get name => 'Dog';
  
  @override
  void makeSound() => print('Woof!');
}

Когда НЕ использовать abstract

// ❌ Не используй abstract если:

// 1. Класс полностью реализован
class User {  // Просто класс, не abstract
  String name;
  int age;
  
  User(this.name, this.age);
}

// 2. Это просто helper класс
class DateUtils {  // Не нужен abstract
  static String formatDate(DateTime date) => date.toString();
  static bool isToday(DateTime date) => date.day == DateTime.now().day;
}

// 3. Нет наследования
class Config {  // Обычный класс
  final String apiUrl;
  final int timeout;
  
  Config({required this.apiUrl, required this.timeout});
}

Лучшие практики с abstract

// ✅ ХОРОШО
abstract class Repository<T> {
  Future<List<T>> getAll();
  Future<T?> getById(int id);
  Future<void> create(T item);
  Future<void> delete(int id);
}

class UserRepository extends Repository<User> {
  @override
  Future<List<User>> getAll() async => [];
  
  @override
  Future<User?> getById(int id) async => null;
  
  @override
  Future<void> create(User item) async {}
  
  @override
  Future<void> delete(int id) async {}
}

// ✅ Частичная реализация
abstract class BaseRepository<T> implements Repository<T> {
  final DatabaseService _db;
  
  BaseRepository(this._db);
  
  @override
  Future<void> delete(int id) async {
    await _db.delete('${T}_$id');
  }
  
  // Подклассы реализуют только getAll, getById, create
}

class UserRepository extends BaseRepository<User> {
  UserRepository(super.db);
  
  @override
  Future<List<User>> getAll() async => [];
  
  @override
  Future<User?> getById(int id) async => null;
  
  @override
  Future<void> create(User item) async {}
}

Почему именно в Dart 2.12?

Dart 2.12 был переломным моментом:

  • Null-safety требовал более строгой типизации
  • Лучше отделить контракты от реализации
  • Повысить безопасность кода на уровне компилятора
  • Избежать runtime ошибок (как UnimplementedError)
// До: полагались на runtime
class OldLogger {
  void log(String msg) => throw UnimplementedError();
}

// После: гарантия на compile-time
abstract class NewLogger {
  void log(String msg);
}

Резюме

abstract в Dart 2.12 появился для:

  1. Явности — ясно видно, это интерфейс или реализация
  2. Безопасности — нельзя создать экземпляр абстрактного класса
  3. Null-safety — более строгая типизация
  4. Best practices — поддержка правильной архитектуры
  5. Compile-time гарантии — ошибки ловятся сразу

Правило: Используй abstract для классов, предназначенных для наследования/реализации, и обычные классы для готовых к использованию типов.