On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is an Abstract Class?
- 2. Declaring an Abstract Class and Method
- 3. You Cannot Create an Abstract Object
- 4. Subclasses Must Implement Every Abstract Member
- 5. Abstract Getters
- 6. Concrete Methods Inside an Abstract Class
- 7. An Abstract Class as a Type
- 8. Fields and Constructors in an Abstract Class
- 9. Abstract Methods Belong Only in Abstract Classes
- 10. Abstract Subclasses
- 11. Other Class Modifiers
- 12. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 62 — Abstract Classes
| Category | 10 — Advanced OOP |
| Lecture | 062 of 140 |
| Estimated video length | 12 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
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:
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());
}0.0
0.0Two 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:
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());
}12.56double 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:
abstract class Shape {
double area();
}
void main() {
var shape = Shape();
print(shape.area());
}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:
abstract class Shape {
double area();
}
class Rectangle extends Shape {
Rectangle(this.width, this.height);
final double width;
final double height;
}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:
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());
}20.05. Abstract Getters
A getter can be abstract too — just leave out the => part:
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()}');
}Square: 9.06. 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:
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();
}Circle with area 3.1
Rectangle with area 6.0describe 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:
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');
}Total area: 23.0The 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:
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()}');
}Employee record created for Sara
Sara: 10000.0The 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:
class Shape {
double area();
}Error: The non-abstract class 'Shape' is missing implementations for these members:
- Shape.areaEither 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:
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()}');
}Coin: 12.56RoundShape 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
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();
}
}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
abstract class Animal {
void speak();
}
class Dog extends Animal {
@override
void speak() {
print('Woof');
}
}
void main() {
Dog().speak();
}WoofExample 2 — Two Subclasses, One Contract
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();
}Meow
MooExample 3 — An Abstract Getter
abstract class Vehicle {
int get wheels;
}
class Bike extends Vehicle {
@override
int get wheels => 2;
}
void main() {
print('A bike has ${Bike().wheels} wheels');
}A bike has 2 wheelsExample 4 — Shared Method Calling an Abstract One
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');
}Hello, Zayan!Example 5 — Constructor in the Abstract Class
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()}');
}Kiwi says SquawkExample 6 — Overriding toString() in the Parent
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));
}Square(16.0)Example 7 — Student Marks
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();
}Ali passed with 45 (pass mark 40)
Sara failed with 45 (pass mark 50)Example 8 — Shopping Total
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));
}
}2700.0
2500.0Example 9 — Bank Accounts
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();
}Ali: 4900.0
Sara: 80000.0Example 10 — Temperature Sensors
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();
}
}Kitchen: 34.5 C (HOT)
Bedroom: 22.0 C (OK)Common Beginner Mistakes
1. Trying to create the abstract class
// var shape = Shape(); // ✗
var shape = Circle(2); // ✓ create a concrete subclassError: The class 'Shape' is abstract and can't be instantiated.2. Forgetting one of the abstract members
abstract class Shape {
double area();
double get perimeter;
}
// class Square extends Shape { ... only area ... } // ✗Error: The non-abstract class 'Square' is missing implementations for these members:
- Shape.area
- Shape.perimeterDart lists every missing member. Implement them all, or mark the subclass abstract.
3. Writing abstract in front of a method
abstract class Shape {
// abstract double area(); // ✗
double area(); // ✓
}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 ;
abstract class Shape {
// double area() {} // ✗ a body that returns nothing
double area(); // ✓
}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
class Circle extends Shape {
@override
// double area() => super.area() + 1; // ✗ the parent has no code to run
double area() => 3.14 * radius * radius; // ✓
}Error: Superclass has no method named 'area'.6. Not passing the abstract parent's constructor arguments
abstract class Employee {
Employee(this.name);
final String name;
}
class Manager extends Employee {
// Manager(String name); // ✗
Manager(super.name); // ✓
}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:
Ali: 8000.0
Zayan: 3000.0Try it yourself before opening the solution.
Show solution
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();
}
}Ali: 8000.0
Zayan: 3000.0printPayslip 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
- Create an abstract class
Instrumentwith an abstract methodvoid play(). CreateGuitarthat printsStrum strum, and call it. - What error do you get from
var i = Instrument();? Explain it in one sentence. - Create an abstract class
Planetwith an abstract getterString get name, and a classEarththat returns'Earth'. Print it.
Application
-
Give
Shapean abstractdouble area()and a concretebool isBig()that returnstruewhen the area is over50. CreateSquare(8)and printisBig(). -
Create an abstract class
Fruit(this.name)with an abstractint get calories, andBanana()that callssuper('Banana')and returns105. PrintBanana: 105 calories. -
This program does not compile. Fix it without removing
abstract:abstract class Shape { double area(); double perimeter(); } class Square extends Shape { Square(this.side); final double side; @override double area() => side * side; } -
Store
Square(2)andSquare(5)in aList<Shape>(use your class from question 6) and print the sum of their perimeters.
Challenge
-
Create abstract
Ticket(this.passenger)with an abstractdouble get priceand a concreteprintTicket(). CreateEconomy(price 5000) andBusiness(price 15000). Print:Ali: 5000.0andSara: 15000.0. -
Create abstract
Animalwith abstractString get soundandint get legs. Create an abstractBird extends Animalthat setslegsto2, andCrow extends Birdwith soundCaw. PrintCaw on 2 legs. -
Predict the output, then explain why the first line appears:
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
abstract class Instrument {
void play();
}
class Guitar extends Instrument {
@override
void play() {
print('Strum strum');
}
}
void main() {
Guitar().play();
}Strum strumQuestion 2 — Solution
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
abstract class Planet {
String get name;
}
class Earth extends Planet {
@override
String get name => 'Earth';
}
void main() {
print(Earth().name);
}EarthQuestion 4 — Solution
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());
}trueQuestion 5 — Solution
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');
}Banana: 105 caloriesQuestion 6 — Solution
Square is missing perimeter, so it must implement it:
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());
}12.0Question 7 — Solution
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);
}28.0Question 8 — Solution
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();
}Ali: 5000.0
Sara: 15000.0Question 9 — Solution
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');
}Caw on 2 legsQuestion 10 — Solution
Setting up Lamp
Lamp is onLamp() 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
- Which keyword makes a class abstract, and where does it go?
- How does an abstract method look different from a normal one?
- Why can't you write
Shape()whenShapeis abstract? - What must a concrete subclass do with every abstract member?
- Can an abstract class contain methods with bodies?
- Can an abstract class have a constructor? Who calls it?
- Can you declare a variable or list of an abstract type?
- What can a subclass do if it implements only part of the contract?
Key Takeaways
abstract classdescribes 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.