| |

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

FeatureSyntax
Declare abstract classabstract class Name { }
Abstract methodReturnType method();
Abstract getterType get name;
Concrete methodReturnType method() { }
ConstructorName(params);
Fieldfinal Type field;
Cannot instantiateName() → compile error

Inheritance vs Implementation

KeywordInherits ImplementationInherits InterfaceMultiple
extendsYesYesNo
implementsNoYesYes
with (mixin)YesYesYes

Concrete Subclass Rules

RequirementConsequence
Implement all abstract membersClass can be concrete
Miss one abstract memberClass must be abstract
Use @overrideRecommended for each
Call super constructorRequired if no default

Abstract Class vs Interface

AspectAbstract ClassInterface (implements)
ImplementationPartialNone
ConstructorYesNo
FieldsYesNo (contract only)
MultipleNoYes
UseShared basePure 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

PitfallWhy It HappensFix
Cannot instantiate abstract classabstract keywordCreate concrete subclass
Abstract member not implementedSubclass is concreteImplement or make abstract
Super constructor not calledAbstract class has constructorAdd : super(...) or super.param
implements doesn’t share codeInterface onlyUse extends for sharing
Multiple inheritance errorDart single inheritanceUse implements or mixins
Forgot @overrideSilent bugAdd annotation
Abstract getter not implementedConcrete subclassImplement 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

ItemValue
Abstract classabstract class Name { }
Abstract methodType method(); (no body)
Abstract getterType get name;
Cannot instantiateYes
Can have constructorYes
Can have concrete membersYes
Subclass must implementAll abstract members
Subclass may stay abstractIf it misses members
Implements keywordAdopts interface only
Multiple interfacesAllowed
Extends + implementsAllowed
Extends + withAllowed
Single inheritanceYes

Key takeaways:

  • Abstract classes cannot be instantiated. They exist to be extended. The abstract keyword 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 implements keyword lets a class adopt that interface without inheriting implementation. A class can implement multiple interfaces.
  • extends is for “is a” relationships with shared code. implements is for pure contracts. with is 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(...) or super.param.
  • @override documents 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!