Dart Tutorial

Dart Lesson 48 of 102 3 min read

Abstract Classes and Methods in Dart

Learn abstract classes in Dart: define methods without a body, force subclasses to implement them, and share common code.

On this page

An abstract class is a class that is incomplete on purpose. You cannot create an object of it directly. It exists to be extended, and it can declare methods that subclasses must provide.

The problem #

What should area() return for a generic Shape? There is no sensible answer. Returning 0 is a lie, and nothing stops a subclass from forgetting to override it.

Abstract methods #

An abstract method has a signature but no body. It ends with a semicolon.

abstract class Shape {
  double area(); // abstract: no body

  void describe() { // a normal method, shared by all shapes
    print('$runtimeType with area ${area().toStringAsFixed(2)}');
  }
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;
}

class Rectangle extends Shape {
  final double width, height;
  Rectangle(this.width, this.height);

  @override
  double area() => width * height;
}

void main() {
  // var s = Shape(); // error: abstract classes can't be instantiated

  List<Shape> shapes = [Circle(1), Rectangle(3, 4)];
  for (final shape in shapes) {
    shape.describe();
  }
}
Circle with area 3.14
Rectangle with area 12.00

If Rectangle left out area(), the code would not compile. The abstract class is a contract enforced by the compiler.

Mixing shared code with required pieces #

Abstract classes are at their best when most of the logic is common and only one or two steps differ. The parent defines the overall process and the children fill in the gaps.

abstract class Report {
  final String title;
  Report(this.title);

  // Each kind of report supplies these.
  List<String> rows();
  String get footer;

  // The shared process.
  void printReport() {
    print('== $title ==');
    rows().forEach(print);
    print(footer);
  }
}

class SalesReport extends Report {
  SalesReport() : super('Sales');

  @override
  List<String> rows() => ['Pens: 120', 'Books: 45'];

  @override
  String get footer => 'Total items: 165';
}

void main() {
  SalesReport().printReport();
}
== Sales ==
Pens: 120
Books: 45
Total items: 165

Abstract classes can declare abstract getters and setters too, as footer shows.

Abstract classes can have constructors and fields #

You cannot call the constructor directly, but subclasses call it through super.

abstract class Account {
  final String owner;
  double balance;

  Account(this.owner, this.balance);

  double monthlyFee();

  void chargeFee() {
    balance -= monthlyFee();
    print('$owner: $balance');
  }
}

class Savings extends Account {
  Savings(super.owner, super.balance);

  @override
  double monthlyFee() => 0;
}

class Business extends Account {
  Business(super.owner, super.balance);

  @override
  double monthlyFee() => balance > 100000 ? 0 : 500;
}

void main() {
  Savings('Mina', 2000).chargeFee();
  Business('Shop', 30000).chargeFee();
}
Mina: 2000.0
Shop: 29500.0

Abstract class or interface? #

extends an abstract classimplements an interface
Inherits method bodies and fieldsYesNo, must write everything
How manyOneMany
Relationship“is a kind of”“can do”

Use an abstract class when subclasses share real code. Use an interface when unrelated classes only need to promise the same methods.

Try it yourself #

Write an abstract class Employee with a name, an abstract double pay() method and a concrete void printPayslip(). Create Salaried (fixed monthly amount) and Hourly (hours times rate) subclasses and print a payslip for each.

Practise in the playground Updated by Santosh Adhikari