Dart 24 🎯 Abstract Classes, Abstract Methods, and Interface Contracts
An abstract class is a class that cannot be instantiated. It exists to be extended. It defines an interface—a set of methods and getters that subclasses must implement—and it may provide partial implementation for those subclasses to inherit. This is the mechanism Dart uses to express contracts: “any class that claims to be a Shape must provide an area getter” or “any class that claims to be a Repository must provide findById and save.”
This chapter covers abstract classes, abstract methods, and the interface contracts they define. You will learn the syntax for declaring abstract classes, how abstract methods differ from concrete ones, how a class can implement an interface without inheriting implementation, and how Dart’s implicit interfaces make every class usable as a contract. You will also see the design patterns that abstract classes enable and the situations where an interface—or a mixin—is the better choice.
Key point: An abstract class can declare methods without bodies. These abstract methods must be implemented by any concrete subclass. A class that contains at least one abstract method must itself be declared abstract. Abstract classes cannot be instantiated, but they can have constructors, fields, and concrete methods that subclasses inherit.
Why abstract classes exist
The contract problem. In a large codebase, multiple classes often need to guarantee that they provide the same set of operations. A payment processor, a shipping provider, and a notification service might all need a process method. Without a contract, there is no way to enforce this. Abstract classes let you declare the required methods once, and any class that extends the abstract class must provide them.
The partial implementation problem. Sometimes a group of related classes shares both an interface and some implementation. A base Shape class might provide a describe() method that formats the shape’s name and area, while requiring each subclass to provide its own area calculation. Abstract classes support this mix of abstract and concrete members. The subclass implements what is unique and inherits what is shared.
The instantiation problem. Some classes are incomplete by design. A Shape has no meaningful area without knowing what kind of shape it is. Instantiating a generic Shape would produce an object with no useful behavior. The abstract keyword prevents this: the class exists only as a base for concrete subclasses.
The interface problem. Dart has no separate interface keyword. Every class implicitly defines an interface that other classes can implement. The implements keyword lets a class adopt that interface without inheriting any implementation. This means any class can serve as a contract, and abstract classes are the common way to declare contracts that have no default implementation.
The polymorphism problem. Abstract classes enable polymorphism with a clear contract. A function that accepts a Shape can call shape.area and trust that every concrete Shape provides it. Without an abstract declaration, there is no compile-time guarantee that the method exists.
a. Declaring abstract classes and methods
An abstract class is declared with the abstract keyword. It cannot be instantiated. It may contain abstract methods—methods with no body—and concrete methods with implementations.
abstract class Shape {
String get name;
double get area;
double get perimeter;
String describe() => '$name: area=${area.toStringAsFixed(2)}';
}
The name, area, and perimeter getters are abstract. They have no body. Any concrete subclass must implement all three. The describe method is concrete—it has a body and uses the abstract getters. Subclasses inherit it and may override it if needed.
A concrete subclass implements all abstract members:
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
String get name => 'Circle';
@override
double get area => 3.14159 * radius * radius;
@override
double get perimeter => 2 * 3.14159 * radius;
}
The @override annotation is recommended for each implemented member. The analyzer verifies that they match the abstract declarations.
A subclass that does not implement all abstract members must itself be abstract:
abstract class ThreeDShape extends Shape {
double get volume;
// Does not implement area or perimeter, so it stays abstract
}
Abstract classes can have constructors. The constructor runs when a concrete subclass is instantiated, initializing inherited fields.
abstract class Shape {
final String color;
Shape(this.color);
double get area;
}
class Circle extends Shape {
final double radius;
Circle(this.radius, super.color);
@override
double get area => 3.14159 * radius * radius;
}
The Circle constructor forwards color to the Shape constructor with super.color. The abstract class’s field is initialized, and the concrete class adds its own.
b. Implicit interfaces and the implements keyword
Every class in Dart defines an implicit interface. The interface consists of all the class’s public members—methods, getters, setters, and fields. The implements keyword lets a class adopt that interface without inheriting any implementation.
class Logger {
void log(String message) => print('[LOG] $message');
void error(String message) => print('[ERROR] $message');
}
class ConsoleLogger implements Logger {
@override
void log(String message) => print('Console: $message');
@override
void error(String message) => print('Console ERROR: $message');
}
ConsoleLogger implements Logger without extending it. It must provide both methods. It does not inherit Logger‘s implementations. The implements relationship is purely about the interface.
A class can implement multiple interfaces:
class DataStore implements Logger, Serializable {
@override
void log(String message) { }
@override
void error(String message) { }
@override
String serialize() => '';
}
Dart allows multiple implements clauses separated by commas. This is how a class satisfies multiple contracts. Because interfaces have no implementation, there is no diamond problem.
When you use implements with an abstract class, you are adopting the abstract class’s interface. The abstract class’s concrete methods are not inherited; the implementing class must provide its own versions.
abstract class Repository<T> {
T? findById(int id);
void save(T item);
}
class MemoryRepository<T> implements Repository<T> {
final _items = <int, T>{};
@override
T? findById(int id) => _items[id];
@override
void save(T item) { }
}
The MemoryRepository implements the Repository interface. It provides both methods. It does not inherit anything from Repository because Repository is abstract and has no implementation.
A class can both extend and implement:
class Base {
void shared() => print('shared');
}
abstract class Contract {
void required();
}
class Combined extends Base implements Contract {
@override
void required() => print('required');
}
Combined inherits shared from Base and must implement required from Contract.
c. Abstract classes versus interfaces versus mixins
Dart offers three mechanisms for sharing behavior and defining contracts: abstract classes, interfaces (via implements), and mixins (via with). Each serves a different purpose.
Abstract classes provide both a contract and partial implementation. Use them when subclasses share code and must implement some members. A subclass can extend only one abstract class.
abstract class Animal {
String get name;
void speak() => print('$name makes a sound');
}
Interfaces provide only a contract. Use implements when you want to adopt an interface without inheriting implementation. A class can implement multiple interfaces.
abstract class Serializable {
String serialize();
}
class User implements Serializable {
@override
String serialize() => '...';
}
Mixins provide implementation that can be shared across unrelated classes. Use with when you want to add behavior without establishing an “is a” relationship. A class can use multiple mixins.
mixin Loggable {
void log(String message) => print('[LOG] $message');
}
class Service with Loggable {
void doWork() {
log('working');
}
}
The choice depends on the relationship. If the subclass “is a” superclass and shares implementation, use extends. If the class “behaves like” an interface without sharing implementation, use implements. If the class needs reusable behavior without a type relationship, use with.
Abstract classes are the most common way to declare contracts that also carry partial implementation. Interfaces are the lightest contract—just a list of required members. Mixins are for code reuse across unrelated types.
A class can combine all three:
abstract class Base {
void required();
}
mixin Helper {
void assist() => print('assisting');
}
class Concrete extends Base with Helper implements Serializable {
@override
void required() => print('required');
@override
String serialize() => '';
}
This class extends Base, mixes in Helper, and implements Serializable. It inherits assist, implements required and serialize, and has access to all three contracts.
Complete Example Session
// ============================================
// PART 1: BASIC ABSTRACT CLASS
// ============================================
abstract class Shape {
double get area;
}
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double get area => 3.14159 * radius * radius;
}
// ============================================
// PART 2: MULTIPLE ABSTRACT MEMBERS
// ============================================
abstract class Shape {
String get name;
double get area;
double get perimeter;
}
// ============================================
// PART 3: CONCRETE SUBCLASS IMPLEMENTING ALL
// ============================================
class Rectangle extends Shape {
final double width;
final double height;
Rectangle(this.width, this.height);
@override
String get name => 'Rectangle';
@override
double get area => width * height;
@override
double get perimeter => 2 * (width + height);
}
// ============================================
// PART 4: ABSTRACT CLASS WITH CONSTRUCTOR
// ============================================
abstract class Shape {
final String color;
Shape(this.color);
double get area;
}
class Circle extends Shape {
final double radius;
Circle(this.radius, super.color);
@override
double get area => 3.14159 * radius * radius;
}
// ============================================
// PART 5: CONCRETE METHOD IN ABSTRACT CLASS
// ============================================
abstract class Shape {
String get name;
double get area;
String describe() => '$name has area ${area.toStringAsFixed(2)}';
}
// ============================================
// PART 6: ABSTRACT SUBCLASS
// ============================================
abstract class ThreeDShape extends Shape {
double get volume;
// Still abstract: does not implement name, area
}
// ============================================
// PART 7: IMPLEMENTS KEYWORD
// ============================================
class ConsoleLogger implements Logger {
@override
void log(String message) => print('Console: $message');
}
class Logger {
void log(String message) { }
}
// ============================================
// PART 8: MULTIPLE INTERFACES
// ============================================
abstract class Serializable {
String serialize();
}
abstract class Comparable<T> {
int compareTo(T other);
}
class Version implements Serializable, Comparable<Version> {
final int major;
Version(this.major);
@override
String serialize() => 'v$major';
@override
int compareTo(Version other) => major - other.major;
}
// ============================================
// PART 9: EXTENDS + IMPLEMENTS
// ============================================
class Base {
void shared() => print('shared');
}
abstract class Contract {
void required();
}
class Combined extends Base implements Contract {
@override
void required() => print('required');
}
// ============================================
// PART 10: POLYMORPHISM WITH ABSTRACT CLASS
// ============================================
void printShapeInfo(Shape shape) {
print('${shape.name}: ${shape.area}');
}
void main() {
final shapes = <Shape>[
Circle(1),
Rectangle(2, 3),
];
shapes.forEach(printShapeInfo);
}
The ten parts covered basic abstract classes, multiple abstract members, concrete subclasses, constructors in abstract classes, concrete methods in abstract classes, abstract subclasses, the implements keyword, multiple interfaces, combining extends and implements, and polymorphism.
Quick Reference
Abstract Class Syntax
| Feature | Syntax |
|---|---|
| Declare abstract class | abstract class Name { } |
| Abstract method | ReturnType method(); |
| Abstract getter | Type get name; |
| Concrete method | ReturnType method() { } |
| Constructor | Name(params); |
| Field | final Type field; |
| Cannot instantiate | Name() → compile error |
Inheritance vs Implementation
| Keyword | Inherits Implementation | Inherits Interface | Multiple |
|---|---|---|---|
extends | Yes | Yes | No |
implements | No | Yes | Yes |
with (mixin) | Yes | Yes | Yes |
Concrete Subclass Rules
| Requirement | Consequence |
|---|---|
| Implement all abstract members | Class can be concrete |
| Miss one abstract member | Class must be abstract |
Use @override | Recommended for each |
| Call super constructor | Required if no default |
Abstract Class vs Interface
| Aspect | Abstract Class | Interface (implements) |
|---|---|---|
| Implementation | Partial | None |
| Constructor | Yes | No |
| Fields | Yes | No (contract only) |
| Multiple | No | Yes |
| Use | Shared base | Pure contract |
Best Practices
✅ Do This:
// Declare abstract classes with the keyword
abstract class Shape {
double get area;
} // ✅
// Use @override on implemented members
@override
double get area => 3.14 * r * r; // ✅
// Provide concrete methods in abstract classes
String describe() => '$name: $area'; // ✅
// Use implements for pure contracts
class MemoryRepo implements Repository { } // ✅
// Use multiple interfaces for multiple contracts
class Version implements Serializable, Comparable<Version> { } // ✅
// Keep abstract classes focused on a single concept
abstract class Repository<T> {
T? findById(int id);
void save(T item);
} // ✅
❌ Don’t Do This:
// Don't instantiate abstract classes
final shape = Shape(); // ❌
// Don't forget to implement all abstract members
class Incomplete extends Shape { } // ❌ if abstract members
// Don't use implements when you want inheritance
class Circle implements Shape { } // ⚠️ no shared code
// Don't make a class abstract without abstract members
abstract class Helper { void help() { } } // ⚠️ unnecessary
// Don't use abstract class for pure interface
abstract class Contract { void doIt(); } // ⚠️ interface preferred
// Don't mix abstract and concrete without clear design
abstract class Mess {
void a();
void b() { }
void c();
} // ⚠️ unclear
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Cannot instantiate abstract class | abstract keyword | Create concrete subclass |
| Abstract member not implemented | Subclass is concrete | Implement or make abstract |
| Super constructor not called | Abstract class has constructor | Add : super(...) or super.param |
implements doesn’t share code | Interface only | Use extends for sharing |
| Multiple inheritance error | Dart single inheritance | Use implements or mixins |
Forgot @override | Silent bug | Add annotation |
| Abstract getter not implemented | Concrete subclass | Implement or stay abstract |
Real-World Examples
1. Shape Hierarchy
abstract class Shape {
double get area;
}
2. Repository Interface
abstract class Repository<T> {
T? findById(int id);
void save(T item);
List<T> getAll();
}
3. Payment Processor
abstract class PaymentProcessor {
Future<bool> charge(double amount);
Future<void> refund(String transactionId);
}
4. Serializable Contract
abstract class Serializable {
Map<String, dynamic> toJson();
}
5. Logger Interface
abstract class Logger {
void log(String level, String message);
}
6. State Machine
abstract class State {
void enter();
void exit();
State? transition(String event);
}
7. Validator
abstract class Validator<T> {
bool validate(T value);
String get errorMessage;
}
8. Factory with Abstract
abstract class Animal {
factory Animal.create(String type) {
return type == 'dog' ? Dog() : Cat();
}
void speak();
}
9. Plugin System
abstract class Plugin {
String get name;
void initialize();
void dispose();
}
10. Builder Pattern
abstract class Builder<T> {
T build();
}
Visual
Abstract Class Hierarchy
┌─────────────────────────────────────────────────────────────┐
│ ABSTRACT CLASS HIERARCHY │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ abstract class Shape │ │
│ │ │ │
│ │ String get name; ← abstract │ │
│ │ double get area; ← abstract │ │
│ │ double get perimeter; ← abstract │ │
│ │ │ │
│ │ String describe() { ← concrete │ │
│ │ return '$name: $area'; │ │
│ │ } │ │
│ └─────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Circle │ │ Rectangle│ │ Triangle │ │
│ │ │ │ │ │ │ │
│ │ name ✓ │ │ name ✓ │ │ name ✓ │ │
│ │ area ✓ │ │ area ✓ │ │ area ✓ │ │
│ │ perim ✓ │ │ perim ✓ │ │ perim ✓ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Each concrete subclass implements all abstract members. │
│ All inherit describe() and can override it. │
│ │
└─────────────────────────────────────────────────────────────┘
Extends vs Implements
┌─────────────────────────────────────────────────────────────┐
│ EXTENDS (Inheritance) │
│ │
│ abstract class Animal { │
│ String name; │
│ void speak() => print('$name speaks'); │
│ void move(); ← abstract │
│ } │
│ │
│ class Dog extends Animal { │
│ @override void move() => print('$name runs'); │
│ } │
│ │
│ Dog inherits: name, speak() │
│ Dog implements: move() │
│ Dog IS AN Animal │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ IMPLEMENTS (Interface) │
│ │
│ abstract class Movable { │
│ void move(); │
│ } │
│ │
│ class Car implements Movable { │
│ @override void move() => print('Car drives'); │
│ } │
│ │
│ Car inherits: nothing │
│ Car implements: move() │
│ Car IS NOT A Movable in the type hierarchy, it just │
│ satisfies the interface contract. │
│ │
└─────────────────────────────────────────────────────────────┘
Abstract vs Concrete Members
┌─────────────────────────────────────────────────────────────┐
│ ABSTRACT CLASS WITH MIXED MEMBERS │
│ │
│ abstract class Vehicle { │
│ final String brand; ← concrete field │
│ Vehicle(this.brand); ← concrete constructor │
│ │
│ int get wheels; ← abstract getter │
│ void start(); ← abstract method │
│ │
│ void describe() { ← concrete method │
│ print('$brand with $wheels wheels'); │
│ } │
│ } │
│ │
│ Subclass must implement: wheels, start │
│ Subclass inherits: brand, describe() │
│ Subclass can override: describe() │
│ │
└─────────────────────────────────────────────────────────────┘
Choosing the Right Mechanism
┌─────────────────────────────────────────────────────────────┐
│ DECISION TREE │
│ │
│ Do you need shared implementation? │
│ │ │
│ ├── YES ──▶ Is the relationship "is a"? │
│ │ ├── YES ──▶ extends (abstract class) │
│ │ └── NO ──▶ with (mixin) │
│ │ │
│ └── NO ──▶ Do you need a contract? │
│ ├── YES ──▶ implements (interface) │
│ └── NO ──▶ No mechanism needed │
│ │
│ extends: is-a + shares code │
│ implements: behaves-like, contract only │
│ with: shares code without type relationship │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Abstract class | abstract class Name { } |
| Abstract method | Type method(); (no body) |
| Abstract getter | Type get name; |
| Cannot instantiate | Yes |
| Can have constructor | Yes |
| Can have concrete members | Yes |
| Subclass must implement | All abstract members |
| Subclass may stay abstract | If it misses members |
| Implements keyword | Adopts interface only |
| Multiple interfaces | Allowed |
| Extends + implements | Allowed |
| Extends + with | Allowed |
| Single inheritance | Yes |
Key takeaways:
- Abstract classes cannot be instantiated. They exist to be extended. The
abstractkeyword prevents direct instantiation and signals that the class is incomplete. - Abstract methods have no body. Any concrete subclass must implement all abstract members. A subclass that does not implement them must itself be abstract.
- Abstract classes can have concrete members. Fields, constructors, and methods with implementations are inherited by subclasses. This is what distinguishes an abstract class from a pure interface.
- Every class defines an implicit interface. The
implementskeyword lets a class adopt that interface without inheriting implementation. A class can implement multiple interfaces. extendsis for “is a” relationships with shared code.implementsis for pure contracts.withis for shared behavior without a type relationship. Choose based on what the subclass actually needs.- Constructors are not inherited. A subclass must call a superclass constructor explicitly if the superclass has no default constructor. Use
: super(...)orsuper.param. @overridedocuments and validates. The analyzer checks that an annotated method actually overrides something. This catches signature mismatches that would otherwise silently create a new method.- Prefer interfaces over abstract classes for pure contracts. If the abstract class has no implementation and no fields, an interface (
implements) is lighter and allows multiple adoption.
Remember: Abstract classes are Dart’s mechanism for expressing contracts that may also carry partial implementation. They sit between pure interfaces—which have no implementation—and concrete classes—which are fully implemented and instantiable. Use them when you have a group of related types that share some behavior but must each provide their own version of other behavior. The abstract members define what subclasses must provide; the concrete members define what they inherit. Combined with implements and mixins, abstract classes give Dart a flexible system for structuring type hierarchies without the complexity of multiple inheritance.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!