Class Modifiers

By Syncfusion®, Inc. · Last updated

How abstract, base, interface, final, sealed and mixin class control who can make, extend or implement a class, and how sealed classes make switch check every case.

What Are Class Modifiers?

Class modifier = a word in front of class that sets rules for how other files may use the class.

  • Can they make objects of it?
  • Can they extend it (be its child)?
  • Can they implement it (sign its contract)?
  • Inside the same file (library), every rule is relaxed. The rules protect your class from outside code.

Simple lesson: a class modifier is a sign on the door: "who may come in, and how".

A Real-World Example

Rooms in a school have different rules.

  • The library is open to all: anyone may enter and borrow. That is a plain class.
  • The science lab may be used only by following the lab's own steps. That is like base.
  • The exam paper store is locked. Nobody outside may change anything. That is final.
  • The list of houses is fixed: Red, Blue, Green, Yellow. Nobody adds a fifth. That is sealed.

Pick a modifier and see what other files are allowed to do:

Every Run box on this page is one file, so the outside-file errors cannot happen there.

When a rule only shows up across two files, this page shows the code in a plain box, with Dart's error message.

What You Will Learn

In this lesson, you will learn:

  1. The modifiers abstract, base, interface and final.
  2. The sealed modifier, and why switch loves it.
  3. Combined modifiers: abstract interface and mixin class.
  4. One table that compares them all.
  5. How to choose the right modifier.

abstract, base, interface and final

You met abstract in Polymorphism. Three more modifiers control the other doors.

base

base class = other files may extend it, but may not implement it.

  • Why? Every child then really runs the parent's code. Nobody can fake it with implements.
  • Children of a base class must themselves be base, final or sealed.

A base class and its child

base class Account {
  int _balance = 0;
  int get balance => _balance;

  void deposit(int rupees) {
    if (rupees > 0) {
      _balance += rupees;
    }
  }
}

base class SavingsAccount extends Account {
  void addInterest() => deposit(balance ~/ 10);
}

void main() {
  var acc = SavingsAccount();
  acc.deposit(500);
  acc.addInterest();
  print(acc.balance);
}

In another file, class FakeAccount implements Account {} gives: The class 'Account' can't be implemented outside of its library because it's a base class.

interface

interface class = other files may implement it, but may not extend it.

  • Use it for a pure contract that others fill in.
  • Nobody can inherit and quietly change half of your code.
// printer.dart
interface class Printer {
  void printPage(String text) => print(text);
}

// main.dart
class LaserPrinter extends Printer {}   // Error: can't be extended outside of its library
class InkPrinter implements Printer {   // OK
  @override
  void printPage(String text) => print('ink: $text');
}

Inside the same file, both are allowed, so the Run box can show a working implementation:

Implementing an interface class

interface class Printer {
  void printPage(String text) => print(text);
}

class InkPrinter implements Printer {
  @override
  void printPage(String text) => print('ink: $text');
}

void main() {
  Printer p = InkPrinter();
  p.printPage('Class 7A timetable');
}

final

final class = other files may not extend and may not implement it. They can only use it.

  • Use it when the class must stay exactly as you wrote it.
  • You can still change its insides later, without breaking anyone's child class.

A final class can still be used

final class ExamTimer {
  final int minutes;
  ExamTimer(this.minutes);

  String get label => '$minutes minutes left';
}

void main() {
  var t = ExamTimer(90);
  print(t.label);
}

Simple lesson: base allows only children, interface allows only contracts, and final allows neither.

sealed

sealed class = a class with a fixed, known list of children, all in the same file.

  • A sealed class is also abstract. You cannot make objects of it.
  • No other file can add a new child.
  • So Dart knows every child. A switch can check you handled them all.

Sealed Classes and Exhaustive switch

Exhaustive switch = a switch that covers every possible case. (You met this with enums in Enums.)

Every kind of result is handled

sealed class ExamResult {}

class Pass extends ExamResult {
  final int marks;
  Pass(this.marks);
}

class Fail extends ExamResult {
  final int marks;
  Fail(this.marks);
}

class Absent extends ExamResult {}

String message(ExamResult r) => switch (r) {
  Pass(marks: var m) => 'Passed with $m',
  Fail(marks: var m) => 'Failed with $m. Try again!',
  Absent() => 'Was absent',
};

void main() {
  var results = [Pass(82), Fail(28), Absent()];
  for (var r in results) {
    print(message(r));
  }
}
  • There is no _ (default) arm, and none is needed.
  • Pass(marks: var m) checks the type and takes out the marks. You will learn this fully in Pattern Matching.

Forgetting a Case

Now imagine someone adds class Withheld extends ExamResult {} to the same file:

String message(ExamResult r) => switch (r) {
  Pass(marks: var m) => 'Passed with $m',
  Fail(marks: var m) => 'Failed with $m. Try again!',
  Absent() => 'Was absent',
};
// Error: The type 'ExamResult' is not exhaustively matched by the switch cases
// since it doesn't match 'Withheld()'.

Real Dart finds the forgotten case before the program runs. That is the reason to use sealed.

The course runner does not check exhaustiveness. It reports a missing case only when it actually happens while running.

Real Dart (and your editor) check it before running, so you see the problem at once.

Simple lesson: sealed fixes the list of children, so switch can make sure none is forgotten.

Combined Modifiers

CombinationMeaning
abstract interface classA pure contract: no objects, and others may only implement it.
abstract base classA plan that others must extend, never fake with implements.
abstract final classNo objects and no children outside: a box for static helpers.
mixin classA class that can also be used after with. (See Mixins.)

abstract final class as a box of helpers

abstract final class Grades {
  static String of(int marks) {
    if (marks >= 90) return 'A';
    if (marks >= 75) return 'B';
    if (marks >= 50) return 'C';
    return 'D';
  }
}

void main() {
  for (var m in [95, 80, 61, 40]) {
    print('$m -> ${Grades.of(m)}');
  }
}

Simple lesson: modifiers can be combined to say exactly what you mean.

The Comparison Table

What code in another file may do:

ModifierMake objects?Extend?Implement?Use for
(none)yesyesyesmost classes
abstractnoyesyesa plan with some shared code
baseyesyesnocode every child must really run
interfaceyesnoyesa contract others fill in
finalyesnonoa class that must stay as written
sealednononoa fixed family for switch
abstract interfacenonoyesa pure contract
mixin classyesyesyesa class that is also a mixin

Choosing a Modifier

  • Start with no modifier. Add one only when you need a rule.
  • A fixed list of kinds (results, shapes, messages)? Use sealed.
  • A contract only? Use abstract interface.
  • Must stay unchanged for others? Use final.

Easy way to remember

  • base = "base your class on mine, do not fake it".
  • interface = "only implement me".
  • final = "finished: use me, do not build on me".
  • sealed = "the family is sealed: no new children".

Simple lesson: pick the smallest rule that protects your class.

TL;DR

  1. Class modifiers set rules for other files: make, extend, implement.
  2. base: extend only. interface: implement only. final: neither.
  3. sealed: a fixed family in one file, abstract, and checked by switch.
  4. Combined: abstract interface, abstract final, mixin class.
  5. Inside the same file, the rules do not apply.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Expecting rules inside one fileModifiers work everywhereThey limit other filesNo error appears in your test file.
var r = ExamResult(); for a sealed classSealed classes make objectsSealed classes are abstractA compile error.
Children of a sealed class in another fileAny file can add oneAll children live in the same fileA compile error.
A plain child of a base classChildren can be plainChildren must be base, final or sealedA compile error.
Adding a _ arm to every sealed switchA default is always saferWithout _, Dart warns you about new childrenNew cases are silently missed.
Using final class for everythingLocking is always goodLock only what needs protectingOther code cannot build on yours.

Check Yourself

Exercises

What to Remember

  • Class modifiers set rules for other files: make objects, extend, implement.
  • base allows extending only, interface allows implementing only, final allows neither.
  • sealed gives a fixed family in one file, and switch can check every child.
  • abstract interface, abstract final and mixin class combine the rules.
  • Start with no modifier, and add the smallest rule you need.

Next: Generics. You will write one class or function that works safely with many types.