Watch the video (12 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is an Abstract Class?
  5. 2. Declaring an Abstract Class and Method
  6. 3. You Cannot Create an Abstract Object
  7. 4. Subclasses Must Implement Every Abstract Member
  8. 5. Abstract Getters
  9. 6. Concrete Methods Inside an Abstract Class
  10. 7. An Abstract Class as a Type
  11. 8. Fields and Constructors in an Abstract Class
  12. 9. Abstract Methods Belong Only in Abstract Classes
  13. 10. Abstract Subclasses
  14. 11. Other Class Modifiers
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 62 — Abstract Classes

Category10 — Advanced OOP
Lecture062 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelIntermediate

Some parents cannot give a real answer: there is no formula for the area of "a shape". An abstract class lets a parent say what every child must do, leave the how to the children, and still share the code they have in common.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.


Prerequisites

Students should already understand, from Lectures 052–061:

  • classes, objects, fields, methods, and constructors
  • getters and toString()
  • extends, super parameters, and parent-typed variables
  • overriding with @override
  • calling parent code with super

Connection With Previous Lectures

Every parent class so far had a real body for each method. Now try to write a parent for circles and rectangles:

dart
class Shape {
  double area() => 0;
}

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

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

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

void main() {
  print(Shape().area());
  print(Rectangle(4, 5).area());
}
output
0.0
0.0

Two wrong answers and no complaint: 0 is a made-up body, and Rectangle silently forgot to override area. We need a way to say: "Shape is only an idea — every real shape must bring its own area."


Learning Objectives

By the end of this lecture you will be able to:

  • Declare an abstract class with abstract class
  • Write abstract methods and abstract getters that have no body
  • Explain why an abstract class cannot be instantiated
  • Implement every abstract member in a concrete subclass, and read the error when one is missing
  • Put shared, concrete methods inside an abstract class
  • Give an abstract class fields and a constructor that subclasses call
  • Use an abstract class as a type for variables and lists
  • Design a small hierarchy with an abstract class as a shared contract

1. What Is an Abstract Class?

An abstract class is a class that describes a family of objects but is never used to create an object itself. It can declare methods without bodies; every non-abstract subclass must supply them.

Analogy — a job advert says "the cashier must take payments and print receipts". It does not take a single payment itself. Each person hired does the actual work, in their own way.


2. Declaring an Abstract Class and Method

Write abstract before class. An abstract method ends with ; instead of a body:

dart
abstract class Shape {
  double area();
}

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

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

void main() {
  var circle = Circle(2);
  print(circle.area());
}
output
12.56

double area(); is a promise: "every shape can tell you its area". Circle keeps the promise by overriding it. A class that is not abstract, like Circle, is called concrete.


3. You Cannot Create an Abstract Object

An abstract class has unfinished methods, so Dart refuses to build one:

dart
abstract class Shape {
  double area();
}

void main() {
  var shape = Shape();
  print(shape.area());
}
output
Error: The class 'Shape' is abstract and can't be instantiated.
  var shape = Shape();
              ^^^^^

Otherwise shape.area() would have no code to run. The error arrives before the program starts, not as a crash later.


4. Subclasses Must Implement Every Abstract Member

Forget an abstract method in a concrete subclass, and Dart lists exactly what is missing:

dart
abstract class Shape {
  double area();
}

class Rectangle extends Shape {
  Rectangle(this.width, this.height);
  final double width;
  final double height;
}
output
Error: The non-abstract class 'Rectangle' is missing implementations for these members:
 - Shape.area
Try to either
 - provide an implementation,
 - inherit an implementation from a superclass or mixin,
 - mark the class as abstract, or
 - provide a 'noSuchMethod' implementation.

This is the check our opening example lacked. The fix is to override area:

dart
abstract class Shape {
  double area();
}

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

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

void main() {
  print(Rectangle(4, 5).area());
}
output
20.0

5. Abstract Getters

A getter can be abstract too — just leave out the => part:

dart
abstract class Shape {
  String get name;
  double area();
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  String get name => 'Square';

  @override
  double area() => side * side;
}

void main() {
  var square = Square(3);
  print('${square.name}: ${square.area()}');
}
output
Square: 9.0

6. Concrete Methods Inside an Abstract Class

An abstract class may also contain normal methods with bodies. They are inherited like any other method — and they may call the abstract ones:

dart
abstract class Shape {
  String get name;
  double area();

  void describe() {
    print('$name with area ${area().toStringAsFixed(1)}');
  }
}

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

  @override
  String get name => 'Circle';

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

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

  @override
  String get name => 'Rectangle';

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

void main() {
  Circle(1).describe();
  Rectangle(2, 3).describe();
}
output
Circle with area 3.1
Rectangle with area 6.0

describe is written once, in the parent; each child supplies only what differs. That is the power of abstract classes: a contract plus shared code.


7. An Abstract Class as a Type

You cannot create a Shape, but you can use Shape as a type. A List<Shape> holds any concrete shape:

dart
abstract class Shape {
  double area();
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  double area() => side * side;
}

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

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

void main() {
  List<Shape> shapes = [Square(2), Rectangle(2, 5), Square(3)];
  double total = 0;
  for (var shape in shapes) {
    total += shape.area();
  }
  print('Total area: $total');
}
output
Total area: 23.0

The loop calls area() without knowing which kind of shape it has — as in Lecture 060, the object decides which version runs. We will study this idea fully in Lecture 065 — Polymorphism.


8. Fields and Constructors in an Abstract Class

An abstract class can have fields and a constructor. You never call that constructor with Shape(...); subclasses call it through super, exactly as in Lecture 061:

dart
abstract class Employee {
  Employee(this.name) {
    print('Employee record created for $name');
  }
  final String name;

  double monthlyPay();
}

class Manager extends Employee {
  Manager(super.name, this.salary);
  final double salary;

  @override
  double monthlyPay() => salary / 12;
}

void main() {
  var manager = Manager('Sara', 120000);
  print('${manager.name}: ${manager.monthlyPay()}');
}
output
Employee record created for Sara
Sara: 10000.0

The parent constructor still runs first and sets up the shared fields for every child.


9. Abstract Methods Belong Only in Abstract Classes

Remove abstract from the class and keep a bodiless method, and Dart treats the class itself as incomplete:

dart
class Shape {
  double area();
}
output
Error: The non-abstract class 'Shape' is missing implementations for these members:
 - Shape.area

Either give the method a body or mark the class abstract.


10. Abstract Subclasses

A subclass that does not implement everything may itself be declared abstract. It fills in part of the contract and leaves the rest to its own children:

dart
abstract class Shape {
  String get name;
  double area();
}

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

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

class Coin extends RoundShape {
  Coin(super.radius);

  @override
  String get name => 'Coin';
}

void main() {
  var coin = Coin(2);
  print('${coin.name}: ${coin.area()}');
}
output
Coin: 12.56

RoundShape supplies area, Coin supplies name. An abstract class may even have no abstract members at all — abstract then simply means "nobody may create one of these directly".


11. Other Class Modifiers

abstract is one of several words that can go before class. We will study implements in Lecture 063 — Interfaces, sealed in Lecture 082 — Sealed Classes, and interface class, base class and final class in Lecture 130 — Class Modifiers.


12. A Real-World Example

dart
abstract class Payment {
  Payment(this.payer, this.amount);
  final String payer;
  final double amount;

  String get method;
  double get fee;

  void process() {
    var total = amount + fee;
    print('$payer paid $amount by $method (fee $fee, total $total)');
  }
}

class CardPayment extends Payment {
  CardPayment(super.payer, super.amount);

  @override
  String get method => 'card';

  @override
  double get fee => amount * 0.02;
}

class CashPayment extends Payment {
  CashPayment(super.payer, super.amount);

  @override
  String get method => 'cash';

  @override
  double get fee => 0;
}

void main() {
  List<Payment> payments = [
    CardPayment('Ali', 2500),
    CashPayment('Sara', 800),
    CardPayment('Zayan', 1000),
  ];
  for (var payment in payments) {
    payment.process();
  }
}
output
Ali paid 2500.0 by card (fee 50.0, total 2550.0)
Sara paid 800.0 by cash (fee 0.0, total 800.0)
Zayan paid 1000.0 by card (fee 20.0, total 1020.0)

Payment holds the shared fields, constructor and process logic. Each child answers just two questions: the method's name and its fee. A new payment type is one small class — and Dart will not compile it until both answers are there.


Code Examples

Example 1 — The Smallest Abstract Class

dart
abstract class Animal {
  void speak();
}

class Dog extends Animal {
  @override
  void speak() {
    print('Woof');
  }
}

void main() {
  Dog().speak();
}
output
Woof

Example 2 — Two Subclasses, One Contract

dart
abstract class Animal {
  void speak();
}

class Cat extends Animal {
  @override
  void speak() {
    print('Meow');
  }
}

class Cow extends Animal {
  @override
  void speak() {
    print('Moo');
  }
}

void main() {
  Cat().speak();
  Cow().speak();
}
output
Meow
Moo

Example 3 — An Abstract Getter

dart
abstract class Vehicle {
  int get wheels;
}

class Bike extends Vehicle {
  @override
  int get wheels => 2;
}

void main() {
  print('A bike has ${Bike().wheels} wheels');
}
output
A bike has 2 wheels

Example 4 — Shared Method Calling an Abstract One

dart
abstract class Greeter {
  String get greeting;

  void greet(String name) {
    print('$greeting, $name!');
  }
}

class EnglishGreeter extends Greeter {
  @override
  String get greeting => 'Hello';
}

void main() {
  EnglishGreeter().greet('Zayan');
}
output
Hello, Zayan!

Example 5 — Constructor in the Abstract Class

dart
abstract class Pet {
  Pet(this.name);
  final String name;

  String sound();
}

class Parrot extends Pet {
  Parrot(super.name);

  @override
  String sound() => 'Squawk';
}

void main() {
  var parrot = Parrot('Kiwi');
  print('${parrot.name} says ${parrot.sound()}');
}
output
Kiwi says Squawk

Example 6 — Overriding toString() in the Parent

dart
abstract class Shape {
  String get name;
  double area();

  @override
  String toString() => '$name(${area()})';
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  String get name => 'Square';

  @override
  double area() => side * side;
}

void main() {
  print(Square(4));
}
output
Square(16.0)

Example 7 — Student Marks

dart
abstract class Student {
  Student(this.name, this.marks);
  final String name;
  final int marks;

  int get passMark;

  void printResult() {
    var result = marks >= passMark ? 'passed' : 'failed';
    print('$name $result with $marks (pass mark $passMark)');
  }
}

class SchoolStudent extends Student {
  SchoolStudent(super.name, super.marks);

  @override
  int get passMark => 40;
}

class UniversityStudent extends Student {
  UniversityStudent(super.name, super.marks);

  @override
  int get passMark => 50;
}

void main() {
  SchoolStudent('Ali', 45).printResult();
  UniversityStudent('Sara', 45).printResult();
}
output
Ali passed with 45 (pass mark 40)
Sara failed with 45 (pass mark 50)

Example 8 — Shopping Total

dart
abstract class Discount {
  double apply(double total);
}

class PercentOff extends Discount {
  PercentOff(this.percent);
  final double percent;

  @override
  double apply(double total) => total - total * percent / 100;
}

class FlatOff extends Discount {
  FlatOff(this.amount);
  final double amount;

  @override
  double apply(double total) => total - amount;
}

void main() {
  double cart = 3000;
  List<Discount> offers = [PercentOff(10), FlatOff(500)];
  for (var offer in offers) {
    print(offer.apply(cart));
  }
}
output
2700.0
2500.0

Example 9 — Bank Accounts

dart
abstract class Account {
  Account(this.owner, this.balance);
  final String owner;
  double balance;

  double get monthlyFee;

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

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

  @override
  double get monthlyFee => 100;
}

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

  @override
  double get monthlyFee => balance > 50000 ? 0 : 300;
}

void main() {
  BasicAccount('Ali', 5000).endOfMonth();
  PremiumAccount('Sara', 80000).endOfMonth();
}
output
Ali: 4900.0
Sara: 80000.0

Example 10 — Temperature Sensors

dart
abstract class Sensor {
  Sensor(this.room);
  final String room;

  double readCelsius();

  void report() {
    var c = readCelsius();
    var status = c > 30 ? 'HOT' : 'OK';
    print('$room: $c C ($status)');
  }
}

class KitchenSensor extends Sensor {
  KitchenSensor() : super('Kitchen');

  @override
  double readCelsius() => 34.5;
}

class BedroomSensor extends Sensor {
  BedroomSensor() : super('Bedroom');

  @override
  double readCelsius() => 22.0;
}

void main() {
  List<Sensor> sensors = [KitchenSensor(), BedroomSensor()];
  for (var sensor in sensors) {
    sensor.report();
  }
}
output
Kitchen: 34.5 C (HOT)
Bedroom: 22.0 C (OK)

Common Beginner Mistakes

1. Trying to create the abstract class

dart
// var shape = Shape(); // ✗
var shape = Circle(2); // ✓ create a concrete subclass
output
Error: The class 'Shape' is abstract and can't be instantiated.

2. Forgetting one of the abstract members

dart
abstract class Shape {
  double area();
  double get perimeter;
}

// class Square extends Shape { ... only area ... } // ✗
output
Error: The non-abstract class 'Square' is missing implementations for these members:
 - Shape.area
 - Shape.perimeter

Dart lists every missing member. Implement them all, or mark the subclass abstract.

3. Writing abstract in front of a method

dart
abstract class Shape {
  // abstract double area(); // ✗
  double area(); // ✓
}
output
Error: Members of classes can't be declared to be 'abstract'.
Try removing the 'abstract' keyword. You can add the 'abstract' keyword before the class declaration.

4. Writing empty braces instead of ;

dart
abstract class Shape {
  // double area() {} // ✗ a body that returns nothing
  double area(); // ✓
}
output
Error: A non-null value must be returned since the return type 'double' doesn't allow null.

{} is a body, so the method is no longer abstract — and it returns nothing.

5. Calling super on an abstract method

dart
class Circle extends Shape {
  @override
  // double area() => super.area() + 1; // ✗ the parent has no code to run
  double area() => 3.14 * radius * radius; // ✓
}
output
Error: Superclass has no method named 'area'.

6. Not passing the abstract parent's constructor arguments

dart
abstract class Employee {
  Employee(this.name);
  final String name;
}

class Manager extends Employee {
  // Manager(String name); // ✗
  Manager(super.name); // ✓
}
output
Error: The implicitly called unnamed constructor from 'Employee' has required parameters.
Try adding an explicit super initializer with the required arguments.

Being abstract does not change the constructor rules from Lecture 061.


Best Practices

  • Make a parent abstract when creating it directly would make no sense ("a shape", "a payment").
  • Use abstract methods for what every child must decide; never fake a body like return 0.
  • Put logic shared by all children in concrete methods of the abstract class.
  • Keep shared fields and their constructor in the abstract parent; children use super..

Mini Challenge

Create an abstract class Employee with a name field, a constructor, an abstract getter double get monthlyPay, and a concrete method printPayslip(). Create FullTime (yearly salary ÷ 12) and PartTime (hours × rate). Store Ali and Zayan in a List<Employee> and print both payslips.

Your program should print exactly:

output
Ali: 8000.0
Zayan: 3000.0

Try it yourself before opening the solution.

Show solution
dart
abstract class Employee {
  Employee(this.name);
  final String name;

  double get monthlyPay;

  void printPayslip() {
    print('$name: $monthlyPay');
  }
}

class FullTime extends Employee {
  FullTime(super.name, this.yearlySalary);
  final double yearlySalary;

  @override
  double get monthlyPay => yearlySalary / 12;
}

class PartTime extends Employee {
  PartTime(super.name, this.hours, this.rate);
  final int hours;
  final double rate;

  @override
  double get monthlyPay => hours * rate;
}

void main() {
  List<Employee> staff = [FullTime('Ali', 96000), PartTime('Zayan', 60, 50)];
  for (var employee in staff) {
    employee.printPayslip();
  }
}
output
Ali: 8000.0
Zayan: 3000.0

printPayslip lives in the parent; each child only says how its pay is calculated.


Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Create an abstract class Instrument with an abstract method void play(). Create Guitar that prints Strum strum, and call it.
  2. What error do you get from var i = Instrument();? Explain it in one sentence.
  3. Create an abstract class Planet with an abstract getter String get name, and a class Earth that returns 'Earth'. Print it.

Application

  1. Give Shape an abstract double area() and a concrete bool isBig() that returns true when the area is over 50. Create Square(8) and print isBig().

  2. Create an abstract class Fruit(this.name) with an abstract int get calories, and Banana() that calls super('Banana') and returns 105. Print Banana: 105 calories.

  3. This program does not compile. Fix it without removing abstract:

    dart
    abstract class Shape {
      double area();
      double perimeter();
    }
    
    class Square extends Shape {
      Square(this.side);
      final double side;
    
      @override
      double area() => side * side;
    }
  4. Store Square(2) and Square(5) in a List<Shape> (use your class from question 6) and print the sum of their perimeters.

Challenge

  1. Create abstract Ticket(this.passenger) with an abstract double get price and a concrete printTicket(). Create Economy (price 5000) and Business (price 15000). Print: Ali: 5000.0 and Sara: 15000.0.

  2. Create abstract Animal with abstract String get sound and int get legs. Create an abstract Bird extends Animal that sets legs to 2, and Crow extends Bird with sound Caw. Print Caw on 2 legs.

  3. Predict the output, then explain why the first line appears:

    dart
    abstract class Device {
      Device(this.name) {
        print('Setting up $name');
      }
      final String name;
      void turnOn();
    }
    
    class Lamp extends Device {
      Lamp() : super('Lamp');
    
      @override
      void turnOn() {
        print('$name is on');
      }
    }
    
    void main() {
      Lamp().turnOn();
    }

Practice Answers

Question 1 — Solution

dart
abstract class Instrument {
  void play();
}

class Guitar extends Instrument {
  @override
  void play() {
    print('Strum strum');
  }
}

void main() {
  Guitar().play();
}
output
Strum strum

Question 2 — Solution

output
Error: The class 'Instrument' is abstract and can't be instantiated.

An abstract class is unfinished — play() has no body — so Dart will not create an object of it.

Question 3 — Solution

dart
abstract class Planet {
  String get name;
}

class Earth extends Planet {
  @override
  String get name => 'Earth';
}

void main() {
  print(Earth().name);
}
output
Earth

Question 4 — Solution

dart
abstract class Shape {
  double area();

  bool isBig() => area() > 50;
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  double area() => side * side;
}

void main() {
  print(Square(8).isBig());
}
output
true

Question 5 — Solution

dart
abstract class Fruit {
  Fruit(this.name);
  final String name;

  int get calories;
}

class Banana extends Fruit {
  Banana() : super('Banana');

  @override
  int get calories => 105;
}

void main() {
  var banana = Banana();
  print('${banana.name}: ${banana.calories} calories');
}
output
Banana: 105 calories

Question 6 — Solution

Square is missing perimeter, so it must implement it:

dart
abstract class Shape {
  double area();
  double perimeter();
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  double area() => side * side;

  @override
  double perimeter() => 4 * side;
}

void main() {
  print(Square(3).perimeter());
}
output
12.0

Question 7 — Solution

dart
abstract class Shape {
  double area();
  double perimeter();
}

class Square extends Shape {
  Square(this.side);
  final double side;

  @override
  double area() => side * side;

  @override
  double perimeter() => 4 * side;
}

void main() {
  List<Shape> shapes = [Square(2), Square(5)];
  double total = 0;
  for (var shape in shapes) {
    total += shape.perimeter();
  }
  print(total);
}
output
28.0

Question 8 — Solution

dart
abstract class Ticket {
  Ticket(this.passenger);
  final String passenger;

  double get price;

  void printTicket() {
    print('$passenger: $price');
  }
}

class Economy extends Ticket {
  Economy(super.passenger);

  @override
  double get price => 5000;
}

class Business extends Ticket {
  Business(super.passenger);

  @override
  double get price => 15000;
}

void main() {
  Economy('Ali').printTicket();
  Business('Sara').printTicket();
}
output
Ali: 5000.0
Sara: 15000.0

Question 9 — Solution

dart
abstract class Animal {
  String get sound;
  int get legs;
}

abstract class Bird extends Animal {
  @override
  int get legs => 2;
}

class Crow extends Bird {
  @override
  String get sound => 'Caw';
}

void main() {
  var crow = Crow();
  print('${crow.sound} on ${crow.legs} legs');
}
output
Caw on 2 legs

Question 10 — Solution

output
Setting up Lamp
Lamp is on

Lamp() calls the abstract parent's constructor through super('Lamp'), and the parent constructor always runs first — being abstract only stops you from calling it as Device(...).


Quick Revision

  1. Which keyword makes a class abstract, and where does it go?
  2. How does an abstract method look different from a normal one?
  3. Why can't you write Shape() when Shape is abstract?
  4. What must a concrete subclass do with every abstract member?
  5. Can an abstract class contain methods with bodies?
  6. Can an abstract class have a constructor? Who calls it?
  7. Can you declare a variable or list of an abstract type?
  8. What can a subclass do if it implements only part of the contract?

Key Takeaways

  • abstract class describes a family of objects and can never be instantiated directly.
  • An abstract method or getter has no body — it ends with ;.
  • Every concrete subclass must implement every abstract member, or Dart refuses to compile.
  • Abstract classes can still hold concrete methods, fields and constructors — shared code.
  • Concrete methods in the parent may call abstract ones; each child fills in only what differs.
  • Subclasses call an abstract parent's constructor through super, as usual.
  • An abstract class works as a type, so one List<Shape> can hold any kind of shape.
  • A subclass may stay abstract and leave part of the contract to its own children.

What's Next?

Lecture 63 — Interfaces. An abstract class gives a contract and shared code, but a Dart class can extend only one parent. What if a class must keep several contracts at once — a Phone that is both a Camera and a MusicPlayer? Next we will learn how implements lets a class promise to follow any number of contracts.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.