Class

What Are Classes and Objects

A class is a category, a classification. It abstracts real-world things into a unified type. For example, we abstract all human beings on Earth into a “Human” class. A specific individual such as Bob or Tom is an object.

A class acts like a “blueprint” or “template” that defines the structure and behaviors of its objects.

Take the Human class as an example: this template outlines what all humans have—names, limbs and organs—and what they can do, like speaking and thinking. Traits such as name and age are class properties, while actions like talking and thinking are class behaviors. In short, a class abstracts real entities into a unified template, which you can then use to create individual objects such as Jack and Jim.

The properties (name, age) and behaviors (speaking, thinking) defined in a class are represented via variables and functions respectively.

It lays out which properties (variables) and methods (functions) every object should possess.

A class is comparable to a baking mold, not a tangible finished product. You use the mold to produce many physical loaves of bread one by one.

Early programming languages such as C lacked the concept of classes, relying solely on functions, variables and standalone statements. This paradigm is known as procedural programming. As software grew more complex and feature-rich, developers noticed critical drawbacks to procedural code: massive redundant logic, messy code organization and poor hierarchical structure. Programmers later devised object-oriented programming, which abstracts logic into discrete objects mirroring real-life scenarios. Imagine a small business: at first the owner handles every task alone. Once they hire staff, they delegate accounting work to finance employees and recruitment to HR specialists. This division of labor creates far clearer, more organized workflows.

An object is an Instance of a class.

When you call a constructor or use the new keyword, the class generates a new object instance.

Objects are tangible runtime entities capable of storing data and invoking methods.

Relationship Between Classes and Objects

  • A class is the architectural blueprint; an object is the actual house built from the blueprint.
  • One single class can instantiate countless independent objects, each holding its own unique dataset.
  • All objects instantiated from the same class share its defined behaviors (methods).

Now let’s shift our focus to Dart.

We’ll cover the following core concepts:

Dart is a fully object-oriented language, meaning every value is an instance of a class. With the exception of Null, all classes inherit from the base Object class.

Dart implements inheritance through a combination of base classes and Mixins.

Single inheritance rule: every class can only extend one parent class, yet Mixins enable reusable logic snippets across multiple classes.

Extension Methods: add new functionality to existing classes without modifying their source code or creating subclasses.

Class Modifiers: control whether other libraries can inherit from or implement your class.

Let’s begin exploring class syntax in Dart step by step.

Class Members

Class members consist of methods and variables—they are the core building blocks contained within a class definition.

Use the . operator to access instance members; the null-safe ?. operator prevents null pointer exceptions when accessing nullable objects.

The following complete example demonstrates defining a class, instantiating objects from it, and calling class members via object references:

// Define a class, equivalent to creating a template mold 
class Point {
  double x, y;
  Point(this.x, this.y);

  // Instance method: calculate distance between two coordinate points
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  // Instantiate object p using the Point class to perform operations
  var p = Point(2, 2);
  
  // Read instance variables (implicit getter)
  print(p.y); //2.0

  // Call instance method
  double dist = p.distanceTo(Point(4, 4));
  print(dist); // 8.0

  // Null-safe access with ?.
  Point? nullablePoint;
  var a = nullablePoint?.y; // If object is null, variable a receives null without throwing errors
  print(a); // null
}Code language: Dart (dart)

Constructors

Standard Constructors

Dart differs from other C-style languages by supporting named constructors. While most C-derived languages only allow constructors sharing the exact class name, Dart lets you define constructors with custom identifiers separate from the class name.

Syntax format: ClassName() / ClassName.Identifier();

The new keyword is optional starting from Dart 2.

class Point {
  double x, y; // Two member variables storing class properties 
  
  // Default constructor to initialize x and y
  Point(this.x, this.y);
  
  // Named constructor Point.fromJson: initializes x and y values from JSON data
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;
		
}

void main() {
  var p1 = Point(2, 2);// Invoke default constructor to set x=2, y=2 for p1
  
  var p2 = Point.fromJson({'x': 1, 'y': 2}); // Pass JSON data to named constructor fromJson
  
  // The new keyword is fully optional with identical behavior
  var p3 = new Point(3, 3); // Instantiate via default constructor
  
  print("p1:(${p1.x},${p1.y}), p2:(${p2.x},${p2.y}), p3:(${p3.x},${p3.y})");
  // Output: p1:(2.0,2.0), p2:(1.0,2.0), p3:(3.0,3.0)
}Code language: Dart (dart)

Constant Constructors with const

Prefix a constructor with const to create compile-time constant objects; identical constant parameter sets reuse the same singleton instance.

class ImmutablePoint {
  final double x, y;
  // Constant constructor requirement: all class members must be marked final
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Constant object instances
  var a = const ImmutablePoint(1, 1); // const keyword required when creating constant instances 
  var b = const ImmutablePoint(1, 1);
  print(identical(a, b)); // true, both reference the same object
  
  var m = const ImmutablePoint(1, 1); 
  var n = const ImmutablePoint(2, 1);
  print(identical(m, n)); // false, differing x values create separate instances

  // Omit const for regular objects which do not share singleton instances
  var c = ImmutablePoint(1, 1);
  print(identical(a, c)); //false

  // Inner const may be omitted inside const context blocks
  const pointMap = {
    'p1': [ImmutablePoint(0, 0)],
    'p2': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)]
  };
  
  print(pointMap); //{p1: [Instance of 'ImmutablePoint'], p2: [Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']}

  print(identical(p1, p2)); // false
  print(identical(p2, p3)); // false
  print(identical(p1, p3)); // false
}Code language: Dart (dart)
Constant constructors only reuse identical instances when every input parameter matches exactly; any differing parameter creates independent separate objects

Retrieve runtime object type with runtimeType

Access runtime type information via object.runtimeType; prefer the is type check operator over runtimeType equality comparisons

class Point {
  double x, y;
  Point(this.x, this.y);

  // Instance method: calculate distance between two coordinate points
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  var p = Point(2, 3);
  print("Type of p: ${p.runtimeType}"); // Point

  // Recommended stable type checking syntax
  if (p is Point) {
    print("p is a Point instance");
  }
  
  // Not recommended for production code
  if (p.runtimeType == Point) {
	print("p is a Point instance ");
  }
}Code language: Dart (dart)

Instance Variables

  1. Nullable uninitialized variables default to null; non-nullable variables require explicit initialization
  2. All instance variables generate an implicit getter; non-final / uninitialized late final variables automatically generate a setter
  3. Standard variable initializer expressions cannot reference this; late variable initializers allow access to this
  4. Final instance variables accept only one assignment: at declaration, via constructor parameters, or within constructor initializer lists

Implicit Getters & Setters

class Point {
  double? x; // Default null, auto-generates getter + setter
  double? y;
  double z = 0; // Default value initialized to 0
}

void main() {
  var point = Point();
  point.x = 4; // Implicit setter invocation
  assert(point.x == 4); // Implicit getter invocation
  assert(point.y == null);
  print(point.z); // 0
}
Code language: JavaScript (javascript)

this Access Restrictions in Initializer Expressions

double globalX = 1.5;
class Point {
  // Valid: access global external variable without relying on this
  double? x = globalX;

  // Compile error: standard initializers cannot reference this
  // double? y = this.x;

  // Valid: late-marked variables support this in initializers
  late double? z = this.x;

  // Valid: constructor parameter shorthand this.x is not counted as initializer expression
  Point(this.x, this.y);
  double? y;
}

void main() {
  var p = Point(null, 5);
  print(p.z); // null
}
Code language: JavaScript (javascript)

Three Initialization Patterns for Final Instance Variables

class ProfileMark {
  // Method 1: Direct initialization at variable declaration
  final DateTime start = DateTime.now();
  final String name;

  // Method 2: Assign via constructor parameter
  ProfileMark(this.name);

  // Method 3: Constructor initializer list assignment; unnamed named constructor defaults name to empty string
  ProfileMark.unnamed() : name = '';
}

void main() {
  var m1 = ProfileMark("user1");
  var m2 = ProfileMark.unnamed();
  print(m1.name);
  print(m2.name);
}
Code language: PHP (php)

Implicit Interfaces

Every Dart class automatically defines an implicit interface containing all its public instance members.

  • implements: Implement an interface, requires overriding every single interface method
  • extends: Inherit a parent class, reusing its existing implementation logic
  • Private variables prefixed with _ are not exposed to the implicit interface

1. Implement Single Interface

class Person {
  final String _name;
  Person(this._name);
  // Public interface method
  String greet(String who) => "Hello $who, I'm $_name";
}

// Implement full Person interface, must override all interface members
class Impostor implements Person {
  String get _name => "";
  @override
  String greet(String who) => "Hi $who, guess who?";
}

String greetBob(Person p) => p.greet("Bob");

void main() {
  print(greetBob(Person("Kathy")));
  print(greetBob(Impostor()));
}
Code language: PHP (php)

Mark overridden parent interface methods with the @override annotation, similar to Java. Use the implements keyword when implementing an interface class.

2. Implement Multiple Interfaces

// Abstract base class
abstract class Comparable {
  int compareTo(Object other);
}

// Abstract base class
abstract class Location {
  double get x;
  double get y;
}

// Class implementing two separate interfaces
class Point implements Comparable, Location {

  @override
  double x, y;

  Point(this.x, this.y);

  @override
  int compareTo(Object other) {
    if (other is Point) {
      return (x + y).compareTo(other.x + other.y);
    }
    return -1;
  }
}

void main() {
  var p = Point(2, 3);
  print(p.x);
  print(p.compareTo(Point(1, 1)));
}
Code language: PHP (php)

@override
double x, y;

This annotation signals that these two properties override abstract getter signatures defined in the parent Location interface.

Static Class Variables & Static Methods

1. Static Variables (Class Variables, Single Global Instance)

  • Belong to the class itself rather than individual instances; lazy initialized — allocated only when first accessed
  • Constant static variables follow lowerCamelCase naming conventions as best practice
class Queue {
  static const initialCapacity = 16; // Static constant variable
  static int count = 0; // Static mutable variable
  Queue() {
    count++;
  }
}

void main() {
  assert(Queue.initialCapacity == 16); // Initializes static value on access, default value 16
  Queue(); // Increment static count by 1
  Queue();
  print(Queue.count); // Output: 2
}
Code language: JavaScript (javascript)

2. Static Methods (Class Methods)

  • No implicit this reference; unable to access instance members, only static class variables
  • Invoked directly using syntax ClassName.method()
  • Valid compile-time constants eligible to pass into constant constructors
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  // Static method to calculate distance between two Point instances
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var dist = Point.distanceBetween(a, b);
  print(dist); // Approximate value ~2.828
}Code language: JavaScript (javascript)

Static methods belong to the class, so you must call them via the class name and pass two Point object instances as arguments.

Previous:

Leave a Reply

Your email address will not be published. Required fields are marked *