Dart: Class Modifiers
A modifier is a keyword or attribute that is used to change or specify the behavior, access, or characteristics of a particular element such as a variable, function, class, or method.
Modifiers provide additional information to the compiler or runtime environment about how a particular part should be treated or interacted with.
Modifiers can affect various aspects of a programming element, including visibility, mutability, memory management, and more.
They help define the rules and constraints governing how different parts of a program can interact.
Class modifiers in dart
1. Abstract class
Use the abstract modifier to define an abstract class- a class that can't be instantiated.
Imagine you're building a house. You create a blueprint or plan for how the house should look and function. This blueprint isn't a physical house itself; instead, it's a guide for building one. In Dart, an abstract class is like that blueprint.
Here's the code with comments to explain each part:
void main() {
var obj = B(); // Create an instance of class B
obj.display(); // Call the display method of obj
}
abstract class A {
int x = 10; // Field x with a default value of 10
void display(); // Abstract method display
void disp() {
// Non-abstract method disp
print(x); // Print the value of x
}
}
class B extends A {
void display() {
// Override the display method
print("Abstract class"); // Print "Abstract class"
print(x); // Access the field x from superclass A
}
}
When you run this code, it will print:
Abstract class
10
In this program we have,
The A
abstract class, which contains an integer field x
, an abstract method display()
, and a non-abstract method disp()
.
The B
class, which extends A
and implements the abstract method display()
.
Explanation:
A
abstract class: This class declares an abstract method display()
and a non-abstract method disp()
. It also contains a field x
with a default value of 10
.
B
class: This class extends A
and overrides the abstract method display()
. Inside the display()
method of class B
, it prints "Abstract class" and the value of x
.
The output shows that the display()
method of class B
is invoked, printing "Abstract class" followed by the value of x
, which is inherited from class A
.
2. Dart: base class
You cannot implement a class outside of the library.
Construct ✅
Inherit ✅
Implement 🚫
To enforce inheritance of a class or mixin’s implementation, use the base modifier. A base class disallows implementation outside of its own library. This guarantees:
The base class constructor is called whenever an instance of a subtype of the class is created.
All implemented private members exist in subtypes.
A new implemented member in a
base
class does not break subtypes, since all subtypes inherit the new member.- This is true unless the subtype already declares a member with the same name and an incompatible signature.
You must mark any class which implements or extends a base class as base, final or sealed. This prevents outside libraries from breaking the base class guarantees.
// Library a.dart
base class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// Can be extended
base class Car extends Vehicle {
int passengers = 4;
// ...
}
// ERROR: Cannot be implemented
base class MockVehicle implements Vehicle {
@override
void moveForward() {
// ...
}
}
3. Dart: final class
To close the type hierarchy, use the final
modifier. This prevents subtyping from a class outside of the current library. Disallowing both inheritance and implementation prevents subtyping entirely. This guarantees:
Construct ✅
Inherit 🚫
Implement ✅
You can safely add incremental changes to the API.
You can call instance methods knowing that they haven’t been overwritten in a third-party subclass.
Final classes can be extended or implemented within the same library. The final
modifier encompasses the effects of base
, and therefore any subclasses must also be marked base
, final
, or sealed
// Library a.dart
final class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// ERROR: Cannot be inherited
class Car extends Vehicle {
int passengers = 4;
// ...
}
class MockVehicle implements Vehicle {
// ERROR: Cannot be implemented
@override
void moveForward(int meters) {
// ...
}
}
4. Dart: Mixing class
Mixins are a way of defining code that can be reused in multiple class hierarchies. They are intended to provide member implementations en masse.
To use a mixin, use the with keyword followed by one or more mixin names. The following example shows two classes that use mixins:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
To define a mixin, use the mixin declaration. In the rare case where you need to define both a mixin and a class, you can use the mixin class declaration.
Mixins and mixin classes cannot have an extends clause, and must not declare any generative constructors.
For example:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
Sometimes you might want to restrict the types that can use a mixin. For example, the mixin might depend on being able to invoke a method that the mixin doesn’t define. As the following example shows, you can restrict a mixin’s use by using the on keyword to specify the required superclass:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
In the preceding code, only classes that extend or implement the Musician class can use the mixin MusicalPerformer. Because SingerDancer extends Musician, SingleDancer can mix in MusicalPerformer.
5. Dart: sealed class
To create a known, enumerable set of subtypes, use the sealed
modifier. This allows you to create a switch over those subtypes that is statically ensured to be exhaustive.
The sealed modifier prevents a class from being extended or implemented outside its own library. Sealed classes are implicitly abstract.
They cannot be constructed themselves.
They can have factory constructors.
They can define constructors for their subclasses to use.
Subclasses of sealed classes are, however, not implicitly abstract.
The compiler is aware of any possible direct subtypes because they can only exist in the same library. This allows the compiler to alert you when a switch does not exhaustively handle all possible subtypes in its cases:
sealed class Vehicle {}
class Car extends Vehicle {}
class Truck implements Vehicle {}
class Bicycle extends Vehicle {}
// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();
// Subclasses can be instantiated
Vehicle myCar = Car();
String getVehicleSound(Vehicle vehicle) {
// ERROR: The switch is missing the Bicycle subtype or a default case.
return switch (vehicle) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
Subscribe to my newsletter
Read articles from Jinali Ghoghari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by