Classes in Dart
last modified May 25, 2025
In this article we will explore classes in Dart, a powerful feature of the Dart programming language that enables object-oriented programming (OOP).
Dart Classes Overview
Classes are at the core of object-oriented programming in Dart. They act as blueprints for creating objects that encapsulate data and behavior. This tutorial comprehensively covers working with Dart classes, from basic class definitions to advanced concepts such as mixins, extensions, and abstract classes.
By leveraging Dart's class system, developers can write clean, reusable, and modular code. The ability to implement encapsulation, inheritance, and polymorphism makes Dart's class structure an essential tool for designing scalable applications.
Feature | Description | Example |
---|---|---|
Class Definition | Blueprint for creating objects | class Point { ... } |
Constructors | Methods for initializing objects | Point(this.x, this.y); |
Properties | Variables within a class | double x, y; |
Methods | Functions within a class | void move() { ... } |
Inheritance | Enables creation of subclasses | class Vector extends Point {} |
Mixins | Allows sharing behaviors across classes | class Robot with Walker {} |
Abstract Classes | Defines structure without implementation | abstract class Shape { ... } |
Extensions | Adds functionality to existing classes | extension on String { ... } |
A class in Dart is a user-defined type that combines state (properties) and behavior (methods). It allows developers to model real-world entities, define relationships between objects, and enforce structured programming.
Basic Class Definition
A class in Dart is defined using the class
keyword followed by the
class name. The body of the class contains its properties (data) and methods
(behavior). Here's a simple example of a class representing a point in 2D space:
class Point { // Instance variables double x; double y; // Constructor Point(this.x, this.y); // Instance method void move(double dx, double dy) { x += dx; y += dy; } // Overriding toString() @override String toString() => 'Point($x, $y)'; } void main() { // Create an instance var p = Point(3.0, 4.0); print(p); // Uses toString() // Access properties and methods p.move(1.0, -1.0); print('New position: (${p.x}, ${p.y})'); }
This example demonstrates several key aspects of Dart classes. The
Point
class has two instance variables (x
and
y
), a constructor that initializes these variables, a method to
move the point, and an overridden toString
method.
The main
function shows how to create an instance of the class and
interact with its members. The constructor syntax Point(this.x, this.y)
is a Dart shorthand for assigning constructor parameters to instance variables.
$ dart run basic_class.dart Point(3.0, 4.0) New position: (4.0, 3.0)
Constructors and Initialization
Dart offers several ways to initialize class instances. Constructors can be simple or complex, with options for named parameters, initializer lists, and factory constructors. Here's a more advanced example demonstrating these features:
class Rectangle { final double width; final double height; final String color; // Main constructor Rectangle(this.width, this.height, [this.color = 'black']); // Named constructor Rectangle.square(double size, String color) : this(size, size, color); // Constructor with initializer list Rectangle.fromJson(Map<String, dynamic> json) : width = json['width'], height = json['height'], color = json['color'] ?? 'black'; // Factory constructor factory Rectangle.fromString(String dimensions) { var parts = dimensions.split('x'); return Rectangle( double.parse(parts[0]), double.parse(parts[1]) ); } double get area => width * height; } void main() { var rect1 = Rectangle(10.0, 20.0); var rect2 = Rectangle.square(15.0, 'red'); var rect3 = Rectangle.fromJson({'width': 5.0, 'height': 8.0}); var rect4 = Rectangle.fromString('12x24'); print('Area of rect1: ${rect1.area}'); print('Area of rect2: ${rect2.area}'); print('Area of rect3: ${rect3.area}'); print('Area of rect4: ${rect4.area}'); }
This example shows four different ways to construct Rectangle
objects. The main constructor uses positional parameters with an optional color
parameter. The named constructor Rectangle.square
provides a
convenient way to create square rectangles. The fromJson
constructor demonstrates initializer list syntax for processing input data. The
factory constructor fromString
shows how to implement alternative
object creation logic. The area
getter demonstrates computed
properties.
$ dart run constructors.dart Area of rect1: 200.0 Area of rect2: 225.0 Area of rect3: 40.0 Area of rect4: 288.0
Inheritance and Polymorphism
Dart supports single inheritance where a class can extend one other class. The following example demonstrates inheritance, method overriding, and polymorphism:
class Shape { final String color; Shape(this.color); double get area => 0; void describe() { print('This $runtimeType has color $color and area $area'); } } class Circle extends Shape { final double radius; Circle(String color, this.radius) : super(color); @override double get area => 3.14159 * radius * radius; } class Square extends Shape { final double side; Square(String color, this.side) : super(color); @override double get area => side * side; } void main() { var shapes = [ Circle('red', 5.0), Square('blue', 4.0), Shape('green') ]; for (var shape in shapes) { shape.describe(); } }
The Shape
class serves as the base class with a default
implementation of area
and a describe
method. The
Circle
and Square
classes extend Shape
and override the area
getter with their specific implementations.
The main
function demonstrates polymorphism by treating different
shape types uniformly through the base class interface. The
runtimeType
property shows Dart's built-in support for getting an
object's actual type at runtime.
$ dart run inheritance.dart This Circle has color red and area 78.53975 This Square has color blue and area 16.0 This Shape has color green and area 0
Mixins and Composition
Dart uses mixins to share code across multiple class hierarchies. A mixin is a way to reuse a class's code in multiple class hierarchies without using inheritance. Here's an example demonstrating mixins and composition:
// Mixins can't have constructors mixin Logger { void log(String message) { print('Log: $message - ${DateTime.now()}'); } } mixin JsonSerializable { String toJsonString() { return '{"type": "$runtimeType"}'; } } class Person with Logger { final String name; final int age; Person(this.name, this.age); void celebrateBirthday() { age++; log('$name is now $age years old'); } } class Product with Logger, JsonSerializable { final String id; final double price; Product(this.id, this.price); void applyDiscount(double percent) { final discount = price * (percent / 100); log('Applying $percent% discount (\$$discount) to product $id'); } } void main() { var person = Person('Alice', 30); person.celebrateBirthday(); var product = Product('12345', 99.99); product.applyDiscount(10); print(product.toJsonString()); }
The Logger
and JsonSerializable
mixins provide
reusable functionality that can be added to any class. The Person
class uses only the Logger
mixin, while Product
uses
both mixins. Mixins are included using the with
keyword. This
approach allows for code reuse without the limitations of single inheritance, as
a class can use multiple mixins while also extending another class.
$ dart run mixins.dart Log: Alice is now 31 years old - 2025-05-25 14:30:45.123456 Log: Applying 10% discount ($9.999) to product 12345 - 2025-05-25 14:30:45.123456 {"type": "Product"}
Advanced Class Features
Dart provides several advanced class features including abstract classes, interfaces, extension methods, and operator overloading. The following example demonstrates these concepts:
// Abstract class abstract class Animal { String get name; void makeSound(); } // Interface (implicit in Dart) class Bird { void fly() => print('Flying high'); } // Class implementing multiple interfaces class Parrot extends Animal implements Bird { @override final String name; Parrot(this.name); @override void makeSound() => print('$name says: Squawk!'); @override void fly() => print('$name is flying in circles'); // Operator overloading Parrot operator +(Parrot other) => Parrot('$name & ${other.name}'); } // Extension method extension on Parrot { void repeat(String phrase) { print('$name repeats: $phrase $phrase $phrase'); } } void main() { var polly = Parrot('Polly'); var matey = Parrot('Matey'); polly.makeSound(); polly.fly(); var couple = polly + matey; print('New parrot: ${couple.name}'); // Using extension method polly.repeat('Hello'); }
The Animal
abstract class defines an interface that
Parrot
implements. Dart doesn't have a separate interface keyword -
any class can serve as an interface. The Parrot
class also
implements the Bird
interface. Operator overloading is demonstrated
with the +
operator, and an extension method adds functionality to
the Parrot
class without modifying its source code. These features
provide flexibility in designing class hierarchies and extending existing
functionality.
$ dart run advanced_features.dart Polly says: Squawk! Polly is flying in circles New parrot: Polly & Matey Polly repeats: Hello Hello Hello
Source
Dart Language: Classes
Dart Language: Mixins
Dart Language: Extensions
Classes are fundamental to Dart's object-oriented programming model. They provide encapsulation of data and behavior, support inheritance and polymorphism through class hierarchies, and enable code reuse through mixins. Dart's class system is designed to be clear and expressive while supporting modern programming patterns. Understanding classes is essential for effective Dart programming, from simple data structures to complex application architectures.
Author
List all Dart tutorials.