Abstract Classes & Interfaces
Abstract Classes
An abstract class cannot be instantiated directly — it exists to define a contract that subclasses must fulfill. It can contain both abstract (unimplemented) and concrete (implemented) methods.
dartabstract class Shape { // Abstract — no body, subclasses MUST implement double get area; double get perimeter; // Concrete — provided default implementation String describe() => 'I am a ${runtimeType} with area ${area.toStringAsFixed(2)}'; } class Circle extends Shape { final double radius; Circle(this.radius); double get area => 3.14159 * radius * radius; double get perimeter => 2 * 3.14159 * radius; } class Rectangle extends Shape { final double width, height; Rectangle(this.width, this.height); double get area => width * height; double get perimeter => 2 * (width + height); } // Polymorphism — treat all shapes the same List<Shape> shapes = [Circle(5), Rectangle(4, 6)]; for (final shape in shapes) { print(shape.describe()); }
Interfaces — implements
In Dart, every class is implicitly an interface. You can implement any class, which forces you to provide implementations for all of its members (including concrete ones). No method bodies are inherited.
dart// This class serves as an interface contract abstract interface class Repository<T> { Future<T?> findById(String id); Future<List<T>> findAll(); Future<void> save(T entity); Future<void> delete(String id); } // Implementation — must implement ALL methods class UserRepository implements Repository<User> { final Map<String, User> _store = {}; Future<User?> findById(String id) async => _store[id]; Future<List<User>> findAll() async => _store.values.toList(); Future<void> save(User user) async => _store[user.id] = user; Future<void> delete(String id) async => _store.remove(id); }
abstract interface — The New Dart 3 Keyword
Dart 3 introduced class modifiers for clear intent:
dart// abstract interface = pure interface, cannot be extended only implemented abstract interface class Printable { void print(); } // base = can be extended but not implemented externally base class BaseService { void log(String msg) => print('[LOG] $msg'); } // final = cannot be extended or implemented at all final class ImmutablePoint { final double x, y; const ImmutablePoint(this.x, this.y); } // sealed = all subtypes must be in the same library (enables exhaustive switch) sealed class Result<T> { } class Success<T> extends Result<T> { final T value; Success(this.value); } class Failure<T> extends Result<T> { final String error; Failure(this.error); }
Sealed Classes for Exhaustive Pattern Matching
dartResult<String> fetchData() => Success('Hello Dart!'); void handleResult(Result<String> result) { // Compiler enforces ALL cases are handled — no default needed! switch (result) { case Success(:final value): print('Got: $value'); case Failure(:final error): print('Error: $error'); } }
Summary
Abstract classes define partial implementations with a contract. Interfaces (via implements) enforce a complete contract without any implementation inheritance. Dart 3's class modifiers (abstract, interface, base, final, sealed) give you precise control over how your types can be used and extended — a major step toward safer API design.