DART: Classes and OOP

Dart is a fully object-oriented language where everything is an object. Classes define the blueprint for objects — their properties, constructors, methods, getters, setters, and access modifiers. Understanding OOP in Dart, including encapsulation, inheritance, and static members, is essential for structuring real Flutter applications.

Object-oriented programming in Dart is the foundation of every Flutter application. Widgets, state objects, models, services — they are all classes. A solid understanding of how Dart classes work unlocks the ability to design clean, maintainable application architectures.


What Is a Class?

A class is a blueprint that defines:

  • Properties (attributes) — data the object holds
  • Methods — actions the object can perform
  • Constructors — how to create an instance of the object
  • Access control — what is public and what is private

An object (also called an instance) is a specific realization of a class, with its own set of property values.

md
Class: Person (blueprint)
  --> Properties: name, age
  --> Methods: greet(), birthday()

Object: person1 = Person('Amr', 30)
Object: person2 = Person('Sara', 25)


Defining a Basic Class

dart
class Person {
  // Public properties
  String name;
  int age;

  // Private property (prefixed with _)
  String _id;

  // Constructor
  Person(this.name, this.age, this._id);

  // Method
  void introduce() {
    print('Hi, I am $name and I am $age years old.');
  }
}

void main() {
  var person1 = Person('Amr', 30, 'usr_001');
  person1.introduce();  // Hi, I am Amr and I am 30 years old.

  print(person1.name);  // Amr
  print(person1.age);   // 30
  // print(person1._id); // ERROR: _id is private (only accessible within same file)
}


Constructors

Default Constructor

Dart provides a concise shorthand for assigning constructor parameters directly to instance fields using this.:

dart
class Point {
  double x;
  double y;

  // Shorthand: this.x and this.y assign the parameters to fields automatically
  Point(this.x, this.y);
}

var p = Point(3.0, 4.0);
print(p.x);  // 3.0

Named Constructors

Dart allows multiple constructors on the same class using named constructors. Useful for creating objects in different ways.

dart
class Color {
  int red;
  int green;
  int blue;

  Color(this.red, this.green, this.blue);

  // Named constructor for creating from a hex string
  Color.fromHex(String hex)
      : red = int.parse(hex.substring(0, 2), radix: 16),
        green = int.parse(hex.substring(2, 4), radix: 16),
        blue = int.parse(hex.substring(4, 6), radix: 16);

  // Named constructor for black
  Color.black() : red = 0, green = 0, blue = 0;
}

var white = Color(255, 255, 255);
var blue = Color.fromHex('0000FF');
var black = Color.black();

Constructor with Default Parameter Values

dart
class User {
  String name;
  int age;
  String role;

  User(this.name, [this.age = 18, this.role = 'viewer']);
}

var user1 = User('Amr');
var user2 = User('Sara', 25, 'admin');


Getters and Setters

Getters compute or format a value from existing properties without exposing internal representation. Setters validate input before assigning a value.

dart
class Circle {
  double _radius;

  Circle(this._radius);

  // Getter — computed property
  double get area => 3.14159 * _radius * _radius;
  double get circumference => 2 * 3.14159 * _radius;
  double get radius => _radius;

  // Setter — validates before assigning
  set radius(double value) {
    if (value < 0) {
      throw ArgumentError('Radius cannot be negative');
    }
    _radius = value;
  }
}

void main() {
  var c = Circle(5.0);
  print(c.area);             // 78.539...
  print(c.circumference);    // 31.415...

  c.radius = 10.0;           // Uses the setter
  // c.radius = -1;          // Throws ArgumentError
}


Static Members

Static properties and methods belong to the class itself, not to any instance. They can be called without creating an object.

dart
class MathUtils {
  static const double pi = 3.14159265358979;

  static int square(int x) => x * x;

  static double circleArea(double radius) => pi * radius * radius;

  static int max(int a, int b) => a > b ? a : b;
}

void main() {
  print(MathUtils.pi);             // 3.14159...
  print(MathUtils.square(5));      // 25
  print(MathUtils.circleArea(3));  // 28.274...
  print(MathUtils.max(10, 7));     // 10
}

Static members are useful for utility functions and constants that logically belong to a class but do not depend on instance state.


Encapsulation

Encapsulation means hiding internal implementation details and exposing only what is necessary. In Dart, members are made private by prefixing with _ (underscore).

dart
class BankAccount {
  final String _accountNumber;
  double _balance;

  BankAccount(this._accountNumber, double initialBalance)
      : _balance = initialBalance;

  double get balance => _balance;  // Read-only access to balance

  void deposit(double amount) {
    if (amount <= 0) throw ArgumentError('Deposit must be positive');
    _balance += amount;
  }

  bool withdraw(double amount) {
    if (amount > _balance) return false;  // Insufficient funds
    _balance -= amount;
    return true;
  }
}

void main() {
  var account = BankAccount('ACC-001', 1000.0);
  account.deposit(500.0);
  print(account.balance);          // 1500.0
  print(account.withdraw(200.0));  // true
  // account._balance = 0;         // ERROR: private member
}


Inheritance

Inheritance allows a class to extend another class, inheriting all its properties and methods. The child class can override parent methods or add new ones.

dart
class Animal {
  String name;
  int age;

  Animal(this.name, this.age);

  void eat() {
    print('$name is eating');
  }

  void sleep() {
    print('$name is sleeping');
  }

  @override
  String toString() => 'Animal($name, $age)';
}

class Dog extends Animal {
  String breed;

  Dog(String name, int age, this.breed) : super(name, age);

  void bark() {
    print('$name says: Woof!');
  }

  // Override the parent's eat method
  @override
  void eat() {
    print('$name eats dog food enthusiastically');
  }
}

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

  void purr() {
    print('$name purrs softly');
  }
}

void main() {
  var dog = Dog('Rex', 3, 'Labrador');
  dog.eat();    // Rex eats dog food enthusiastically (overridden)
  dog.bark();   // Rex says: Woof!
  dog.sleep();  // Rex is sleeping (inherited)

  var cat = Cat('Whiskers', 2);
  cat.eat();    // Whiskers is eating (from Animal)
  cat.purr();   // Whiskers purrs softly
}


Abstract Classes

An abstract class defines a contract — it declares methods that subclasses must implement, but does not provide the implementation itself.

dart
abstract class Shape {
  double get area;
  double get perimeter;

  void describe() {
    print('Area: $area, Perimeter: $perimeter');
  }
}

class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double get area => width * height;

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

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

  @override
  double get area => 3.14159 * radius * radius;

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


Interfaces (via implements)

In Dart, every class implicitly defines an interface. Use implements to enforce that a class provides a specific set of methods.

dart
abstract class Serializable {
  Map<String, dynamic> toJson();
  String toJsonString();
}

class Product implements Serializable {
  final int id;
  final String name;
  final double price;

  Product({required this.id, required this.name, required this.price});

  @override
  Map<String, dynamic> toJson() => {'id': id, 'name': name, 'price': price};

  @override
  String toJsonString() => toJson().toString();
}


OOP Principles in Dart

PrincipleDart Feature
Encapsulation_ prefix for private members, getters/setters
Inheritanceextends keyword, super calls
PolymorphismMethod overriding with @override
Abstractionabstract classes, implements

These principles work together to produce code that is modular, testable, and easy to reason about — the foundation of every well-structured Flutter app.