Skip to main content

Classes & OOP


Defining a Class

class Dog {
// Instance variables (fields)
String name;
String breed;
int age;

// Constructor
Dog(this.name, this.breed, this.age);

// Instance methods
void bark() => print('$name says: Woof!');

String describe() => '$name is a $age-year-old $breed.';

// Override toString (from Object)

String toString() => 'Dog($name, $breed, $age)';
}

// Usage
var rex = Dog('Rex', 'Labrador', 3);
rex.bark(); // Rex says: Woof!
print(rex.describe()); // Rex is a 3-year-old Labrador.
print(rex); // Dog(Rex, Labrador, 3)

Instance Variables

class Person {
// Public (default in Dart — no public keyword)
String name;

// Private to the library (prefix with _)
int _age; // accessible within the same file/library

// Final — set once in constructor
final String id;

// Late — initialized later (must be set before use)
late String email;

Person(this.name, this._age, this.id);

int get age => _age; // expose privately stored value
}

var p = Person('Alice', 30, 'user_1');
print(p.name); // Alice

In Dart, _ makes something private to the library (file), not the class.


Inheritance

class Animal {
String name;
Animal(this.name);

void breathe() => print('$name breathes');
void speak() => print('...');


String toString() => 'Animal($name)';
}

class Dog extends Animal {
String breed;

// Call super constructor
Dog(String name, this.breed) : super(name);


void speak() => print('$name says: Woof!');

// New method
void fetch() => print('$name fetches the ball!');
}

class Cat extends Animal {
Cat(String name) : super(name);


void speak() => print('$name says: Meow!');
}

void main() {
var dog = Dog('Rex', 'Labrador');
dog.breathe(); // Rex breathes (inherited)
dog.speak(); // Rex says: Woof! (overridden)
dog.fetch(); // Rex fetches the ball!

// Polymorphism
List<Animal> animals = [Dog('Rex', 'Lab'), Cat('Whiskers')];
for (var a in animals) {
a.speak(); // dynamic dispatch
}
}

The super Keyword

class Vehicle {
String brand;
int speed;

Vehicle(this.brand, this.speed);

void describe() => print('$brand at $speed km/h');
}

class ElectricCar extends Vehicle {
int batteryLevel;

ElectricCar(String brand, int speed, this.batteryLevel)
: super(brand, speed); // call super constructor


void describe() {
super.describe(); // call super method
print('Battery: $batteryLevel%');
}
}

Abstract Classes

Abstract classes cannot be instantiated — they define a contract.

abstract class Shape {
// Abstract method — subclasses MUST implement
double get area;
double get perimeter;

// Concrete method — subclasses inherit
void printInfo() {
print('Area: ${area.toStringAsFixed(2)}, Perimeter: ${perimeter.toStringAsFixed(2)}');
}
}

class Circle extends Shape {
final double radius;
Circle(this.radius);


double get area => 3.14159 * radius * radius;


double get perimeter => 2 * 3.14159 * radius;
}

class Rectangle extends Shape {
final double width, height;
Rectangle(this.width, this.height);


double get area => width * height;


double get perimeter => 2 * (width + height);
}

// var s = Shape(); // ❌ cannot instantiate abstract class
var c = Circle(5);
c.printInfo(); // Area: 78.54, Perimeter: 31.42

Interfaces (implements)

Dart has no interface keyword. Every class implicitly defines an interface.

// "Interface" via abstract class
abstract class Flyable {
void fly();
int get altitude;
}

abstract class Swimmable {
void swim();
int get depth;
}

// A class can implement multiple interfaces
class Duck extends Animal implements Flyable, Swimmable {
Duck(String name) : super(name);


void fly() => print('$name flaps wings!');


int get altitude => 10;


void swim() => print('$name paddles!');


int get depth => 1;
}

// Using interface type
Flyable flyer = Duck('Donald');
flyer.fly();

Sealed Classes (Dart 3)

Sealed classes can only be extended/implemented in the same library. Perfect for exhaustive pattern matching.

sealed class Result<T> {}

class Success<T> extends Result<T> {
final T value;
Success(this.value);
}

class Failure<T> extends Result<T> {
final String error;
Failure(this.error);
}

// Exhaustive switch — no default needed!
String describe<T>(Result<T> result) => switch (result) {
Success(value: var v) => 'Success: $v',
Failure(error: var e) => 'Error: $e',
};

// Add a new subclass → compiler error at switch site → can't miss it!

Object Equality

class Point {
final int x, y;
const Point(this.x, this.y);

// Override == operator

bool operator ==(Object other) =>
other is Point && x == other.x && y == other.y;

// Must override hashCode if overriding ==

int get hashCode => Object.hash(x, y);


String toString() => 'Point($x, $y)';
}

var p1 = Point(1, 2);
var p2 = Point(1, 2);
var p3 = Point(3, 4);

print(p1 == p2); // true (same values)
print(p1 == p3); // false
print(identical(p1, p2)); // false (different objects)

// Can be used as Map keys or Set members since hashCode is consistent
var set = {p1, p2, p3};
print(set.length); // 2 (p1 and p2 are considered equal)

Class Modifiers (Dart 3)

// abstract — can't be instantiated
abstract class A {}

// final — can't be extended or implemented outside library
final class B {}

// sealed — can only be subclassed in same library
sealed class C {}

// base — can be extended but not implemented
base class D {}

// interface — can be implemented but not extended
interface class E {}

// mixin class — can be used as mixin or class
mixin class F {}

Summary

ConceptSyntax
Define classclass Name { ... }
Extend classclass Child extends Parent
Implement interfaceclass Impl implements Interface
Abstract classabstract class Name
Sealed class (Dart 3)sealed class Name
Override method@override void method()
Call supersuper.method()
Private member_memberName
Override equalityoperator == + hashCode