Abstract Classes in Dart
last modified May 25, 2025
This tutorial explores abstract classes in Dart, a key feature for creating class hierarchies and defining interfaces. Abstract classes enable polymorphism and code reuse while enforcing implementation contracts.
Abstract Classes Overview
Abstract classes in Dart serve as incomplete blueprints that cannot be instantiated directly. They define a contract that concrete subclasses must implement. Abstract classes can contain both implemented methods and abstract method declarations.
Unlike regular classes, abstract classes may have unimplemented methods (abstract
methods). These methods must be implemented by any concrete (non-abstract)
subclass. Abstract classes are declared using the abstract
modifier.
Feature | Abstract Class | Regular Class |
---|---|---|
Instantiation | Cannot be instantiated | Can be instantiated |
Methods | Can have abstract methods | All methods implemented |
Purpose | Define interfaces/behavior | Complete implementation |
Inheritance | Must be extended | Can be used directly |
Abstract classes are particularly useful when you want to share code among several related classes while enforcing certain methods to be implemented. They sit between interfaces (pure contracts) and concrete classes (full implementations).
Basic Abstract Class
This example demonstrates a simple abstract class with both concrete and
abstract methods. We'll create an abstract Shape
class that
defines common behavior for all shapes.
abstract class Shape { // Abstract method (no implementation) double area(); // Concrete method void describe() { print('This shape has an area of ${area()}'); } } class Circle extends Shape { final double radius; Circle(this.radius); @override double area() => 3.14159 * radius * radius; } class Square extends Shape { final double side; Square(this.side); @override double area() => side * side; } void main() { // var shape = Shape(); // Error: Can't instantiate abstract class var circle = Circle(5.0); var square = Square(4.0); circle.describe(); square.describe(); }
The Shape
abstract class declares an abstract area
method that subclasses must implement. It also provides a concrete
describe
method that uses the abstract method. This demonstrates
how abstract classes can mix implemented and unimplemented functionality.
The Circle
and Square
classes extend Shape
and provide their specific implementations of area
. The example
shows that we cannot instantiate Shape
directly - we must create
instances of the concrete subclasses.
$ dart run basic_abstract.dart This shape has an area of 78.53975 This shape has an area of 16.0
Abstract Classes as Interfaces
Dart doesn't have a separate interface keyword - abstract classes often serve as interfaces. This example shows how to use abstract classes to define contracts that multiple classes can implement differently.
abstract class PaymentProcessor { void processPayment(double amount); void refundPayment(double amount); } class CreditCardProcessor implements PaymentProcessor { @override void processPayment(double amount) { print('Processing credit card payment: \$$amount'); } @override void refundPayment(double amount) { print('Refunding credit card payment: \$$amount'); } } class PayPalProcessor implements PaymentProcessor { @override void processPayment(double amount) { print('Processing PayPal payment: \$$amount'); } @override void refundPayment(double amount) { print('Refunding PayPal payment: \$$amount'); } } void processOrder(PaymentProcessor processor, double amount) { processor.processPayment(amount); } void main() { var creditCard = CreditCardProcessor(); var paypal = PayPalProcessor(); processOrder(creditCard, 100.0); processOrder(paypal, 50.0); }
The PaymentProcessor
abstract class defines an interface that
payment processors must implement. Unlike with extends
, using
implements
requires the subclass to provide all method
implementations, including concrete ones from the abstract class.
The example demonstrates polymorphism - the processOrder
function
accepts any PaymentProcessor
implementation. This pattern is
useful for creating pluggable architectures where implementations can be
swapped easily.
$ dart run interface_abstract.dart Processing credit card payment: $100.0 Processing PayPal payment: $50.0
Abstract Classes with Properties
Abstract classes can define abstract properties that subclasses must implement. This example shows an abstract class with both abstract and concrete properties.
abstract class Vehicle { // Abstract property String get name; // Concrete property int wheels = 4; // Abstract method void move(); // Concrete method using abstract members void describe() { print('$name with $wheels wheels is moving'); move(); } } class Car extends Vehicle { @override final String name; Car(this.name); @override void move() { print('Vroom vroom!'); } } class Bicycle extends Vehicle { @override final String name = 'Bicycle'; @override int wheels = 2; @override void move() { print('Pedaling...'); } } void main() { var car = Car('Tesla'); var bike = Bicycle(); car.describe(); bike.describe(); }
The Vehicle
abstract class declares an abstract name
property and move
method, while providing a default
wheels
value and concrete describe
method. The
Car
and Bicycle
classes implement these differently.
This demonstrates how abstract classes can define both required and optional
aspects of an interface. The wheels
property shows how concrete
subclasses can override default values from the abstract class.
$ dart run properties_abstract.dart Tesla with 4 wheels is moving Vroom vroom! Bicycle with 2 wheels is moving Pedaling...
Multiple Inheritance with Mixins
Dart's mixins can be used with abstract classes to achieve multiple inheritance- like behavior. This example combines an abstract class with mixins to create flexible class hierarchies.
abstract class Animal { String get name; void makeSound(); } mixin Walker { void walk() { print('Walking...'); } } mixin Swimmer { void swim() { print('Swimming...'); } } class Dog extends Animal with Walker { @override final String name; Dog(this.name); @override void makeSound() => print('Woof!'); } class Duck extends Animal with Walker, Swimmer { @override final String name; Duck(this.name); @override void makeSound() => print('Quack!'); } void main() { var dog = Dog('Buddy'); var duck = Duck('Donald'); dog.makeSound(); dog.walk(); duck.makeSound(); duck.walk(); duck.swim(); // Can't instantiate abstract class // var animal = Animal(); }
The Animal
abstract class defines core animal behavior, while the
Walker
and Swimmer
mixins add specific capabilities.
The Dog
and Duck
classes combine these features
differently.
This pattern allows for flexible code reuse while maintaining a clear class hierarchy. The abstract class ensures certain contracts are fulfilled, while mixins provide modular behavior that can be mixed in as needed.
$ dart run mixin_abstract.dart Woof! Walking... Quack! Walking... Swimming...
Factory Constructors in Abstract Classes
Abstract classes can have factory constructors that return instances of concrete subclasses. This is useful for creating objects without exposing the concrete implementations.
abstract class Logger { void log(String message); factory Logger(String type) { switch (type.toLowerCase()) { case 'console': return ConsoleLogger(); case 'file': return FileLogger(); default: throw ArgumentError('Unknown logger type: $type'); } } } class ConsoleLogger implements Logger { @override void log(String message) { print('CONSOLE: $message'); } } class FileLogger implements Logger { @override void log(String message) { print('FILE: $message (written to log file)'); } } void main() { var consoleLogger = Logger('console'); var fileLogger = Logger('file'); consoleLogger.log('This is a console message'); fileLogger.log('This should go to a file'); try { var unknownLogger = Logger('database'); } catch (e) { print('Error: $e'); } }
The Logger
abstract class defines a factory constructor that
returns different logger implementations based on the input parameter. This
hides the concrete classes from consumers, who only interact with the
abstract interface.
This pattern is useful for creating objects where the exact implementation needs to be determined at runtime. It provides a clean way to encapsulate object creation logic while maintaining a simple interface for clients.
$ dart run factory_abstract.dart CONSOLE: This is a console message FILE: This should go to a file (written to log file) Error: ArgumentError: Unknown logger type: database
Source
Dart Abstract Classes
Dart Mixins
Dart Factory Constructors
Abstract classes are a powerful tool in Dart for creating flexible, maintainable class hierarchies. They enable polymorphism, code reuse, and clear interface definitions. By combining abstract classes with mixins and factory constructors, you can build sophisticated yet clean object-oriented designs.
Author
List all Dart tutorials.