Комментарии (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 появился для:
- Явности — ясно видно, это интерфейс или реализация
- Безопасности — нельзя создать экземпляр абстрактного класса
- Null-safety — более строгая типизация
- Best practices — поддержка правильной архитектуры
- Compile-time гарантии — ошибки ловятся сразу
Правило: Используй abstract для классов, предназначенных для наследования/реализации, и обычные классы для готовых к использованию типов.