DART · STEP 2 · OOP

Dart · OOP

Dart 3.13 (any 3.0+ works): classes, constructors, methods, inheritance, mixins and static members — the JavaScript version next to the null-safe Dart version.

Step 2 of 5shelf© Arthur Bernier Jrrewritten · verified9 code blocks436 words

What you will learn#

  • How to Create a Class
  • How to Instantiate a Class
  • Write a Constructor and Methods
  • Create an Inherited Class
  • Write Static Methods/Properties

Below you'll see JavaScript examples of object-oriented patterns and their Dart counterparts. Dart is a class-based language through and through — every value, including numbers and functions, is an object — so most of this will feel familiar.

Creating and Instantiating a Class#

In Javascript

JS
// Creating a Classclass Dog {} // Instantiationconst Sparky = new Dog();

In Dart

DART
// Creating a classclass Dog {} void main() {  // Instantiation: `new` is optional in Dart and left out by convention  var sparky = Dog();  print(sparky); // Instance of 'Dog'}

Convention: class names are UpperCamelCase, instances and members lowerCamelCase. There is no export keyword — everything in a file is public unless its name starts with _, which makes it private to that file (library).

The Constructor and Methods#

In Javascript

JS
// Creating a Classclass Dog {  // The constructor runs when we instantiate a new instance  constructor(name, age) {    this.name = name;    this.age = age;  }   // A Method  bark() {    console.log(`${this.name} barks`);  }} // Instantiationconst Sparky = new Dog("Sparky", 4);Sparky.bark();

in Dart

DART
class Dog {  // instance fields are declared with their types. With null safety they must be  // initialized — the constructor below does that before its body ever runs.  String name;  int age;   // the constructor is named after the class. `this.name` is an "initializing  // formal": it assigns the parameter straight to the field. No body needed.  Dog(this.name, this.age);   // a method. $name interpolates the field into the string.  void bark() {    print('$name barks!');  }   // what print(dog) shows instead of "Instance of 'Dog'"  @override  String toString() => 'Dog(name: $name, age: $age)';} void main() {  var sparky = Dog('Sparky', 4);  sparky.bark(); // Sparky barks!  print(sparky); // Dog(name: Sparky, age: 4)}

WHY the short constructor: the 2021 version wrote Dog(String name, int age) { this.name = name; this.age = age; }. Dart 3 rejects that — name and age are non-nullable and would be unset while the constructor body runs. Dog(this.name, this.age); initializes them before the body, which is why it's the idiomatic form. Inside methods, this. is only needed when a local variable shadows a field.

Dart 3.13 adds primary constructors, which declare the fields and the constructor in the class header: class Dog(var String name, var int age) { void bark() => print('$name barks!'); }. The longer form above is what you'll see in most code today; both mean the same thing.

Inheritance#

In Javascript

JS
// Creating a Classclass Dog {  // The constructor runs when we instantiate a new instance  constructor(name, age) {    this.name = name;    this.age = age;  }   // A Method  bark() {    console.log(`${this.name} barks`);  }} class SmallDog extends Dog {  constructor(name, age) {    //Super calls the constructor of the parent class    super(name, age);  }   //This will override the version of bark in the parent class  bark() {    console.log(`${this.name} yaps`);  }} // Instantiationconst Sparky = new SmallDog("Sparky", 4);Sparky.bark();

in Dart

DART
class Dog {  String name;  int age;   Dog(this.name, this.age);   void bark() {    print('$name barks!');  }} // extends works like JavaScript: one parent, members inheritedclass SmallDog extends Dog {  // super parameters forward straight to the parent constructor.  // Long form, same meaning: SmallDog(String name, int age) : super(name, age);  SmallDog(super.name, super.age);   // @override is optional, but the analyzer uses it to catch typos in the name  @override  void bark() {    print('$name yaps!');  }} void main() {  var sparky = SmallDog('Sparky', 4);  sparky.bark(); // Sparky yaps!  print(sparky is Dog); // true — `is` is instanceof}

Constructors are not inherited: SmallDog has to declare one that reaches Dog's. Call the parent's version of an overridden method with super.bark().

Dart allows only one extends, but a class can mix in any number of mixins — reusable bundles of methods and fields:

DART
// Dog as defined abovemixin Fetcher {  int ballsFetched = 0;   void fetch() {    ballsFetched++;    print('fetched ball number $ballsFetched');  }} class Retriever extends Dog with Fetcher {  Retriever(super.name, super.age);} void main() {  var goldie = Retriever('Goldie', 3);  goldie.bark(); // from Dog  goldie.fetch(); // from Fetcher}

A mixin can't have a constructor and can't be instantiated on its own; with is how Dart gets the reuse of multiple inheritance without its ambiguity.

Static Methods and Properties#

In Javascript

JS
// Static properties/methods belong to the class, not the instanceclass Calculator {  static lastResult = 0;   // a table of operator functions instead of eval()  static operations = {    "+": (a, b) => a + b,    "-": (a, b) => a - b,    "*": (a, b) => a * b,    "/": (a, b) => a / b,  };   static calculate(num1, num2, operator) {    Calculator.lastResult = Calculator.operations[operator](num1, num2);    return Calculator.lastResult;  }   static showLastResult() {    console.log(Calculator.lastResult);  }} Calculator.calculate(2, 2, "+");Calculator.showLastResult();

In Dart

DART
class Calculator {  // a static property belongs to the class, not to instances.  // num is the parent type of int and double, so 4 / 2 (a double in Dart) fits too.  static num lastResult = 0;   // a static method. Dart has no eval; a switch *expression* picks the operation  // and evaluates to a value. `_` is the default case.  static num calculate(num a, num b, String op) {    lastResult = switch (op) {      '+' => a + b,      '-' => a - b,      '*' => a * b,      '/' => a / b,      _ => throw ArgumentError('Unknown operator: $op'),    };    return lastResult;  }   static void showLastResult() {    print(lastResult);  }} void main() {  Calculator.calculate(2, 2, '+');  Calculator.showLastResult(); // 4  Calculator.calculate(9, 2, '/');  Calculator.showLastResult(); // 4.5}

Static members are reached through the class (Calculator.lastResult), never through an instance, and static methods have no this. Inside the class you can drop the prefix, as calculate does. For class-wide constants use static const (static const maxDigits = 12;). If a "static method" needs nothing from the class at all, Dart style prefers a plain top-level function.


Resources to Learn More#


See also