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:
- The modifiers
abstract,base,interfaceandfinal. - The
sealedmodifier, and whyswitchloves it. - Combined modifiers:
abstract interfaceandmixin class. - One table that compares them all.
- 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
baseclass must themselves bebase,finalorsealed.
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
switchcan 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
| Combination | Meaning |
|---|---|
abstract interface class | A pure contract: no objects, and others may only implement it. |
abstract base class | A plan that others must extend, never fake with implements. |
abstract final class | No objects and no children outside: a box for static helpers. |
mixin class | A 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:
| Modifier | Make objects? | Extend? | Implement? | Use for |
|---|---|---|---|---|
| (none) | yes | yes | yes | most classes |
abstract | no | yes | yes | a plan with some shared code |
base | yes | yes | no | code every child must really run |
interface | yes | no | yes | a contract others fill in |
final | yes | no | no | a class that must stay as written |
sealed | no | no | no | a fixed family for switch |
abstract interface | no | no | yes | a pure contract |
mixin class | yes | yes | yes | a 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
- Class modifiers set rules for other files: make, extend, implement.
base: extend only.interface: implement only.final: neither.sealed: a fixed family in one file, abstract, and checked byswitch.- Combined:
abstract interface,abstract final,mixin class. - Inside the same file, the rules do not apply.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Expecting rules inside one file | Modifiers work everywhere | They limit other files | No error appears in your test file. |
var r = ExamResult(); for a sealed class | Sealed classes make objects | Sealed classes are abstract | A compile error. |
| Children of a sealed class in another file | Any file can add one | All children live in the same file | A compile error. |
A plain child of a base class | Children can be plain | Children must be base, final or sealed | A compile error. |
Adding a _ arm to every sealed switch | A default is always safer | Without _, Dart warns you about new children | New cases are silently missed. |
Using final class for everything | Locking is always good | Lock only what needs protecting | Other code cannot build on yours. |
Check Yourself
Exercises
What to Remember
- Class modifiers set rules for other files: make objects, extend, implement.
baseallows extending only,interfaceallows implementing only,finalallows neither.sealedgives a fixed family in one file, andswitchcan check every child.abstract interface,abstract finalandmixin classcombine 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.