Dart: Objects

Dart: Objects

Understanding classes and objects in Dart.

Here is an overview of Dart's object-oriented features (OOP):

  • Dart is an object-oriented language like Java, C++, etc. It supports all the OOP concepts like classes, inheritance, abstraction, encapsulation, polymorphism, etc.

  • Classes: Dart uses classes to model objects. A class defines the data and behavior of an object.

  • Inheritance: Dart supports single inheritance. A class can inherit from one superclass only. Inheritance allows us to reuse the properties and behaviors of an existing class.

  • Abstraction: Abstraction is achieved in Dart using abstract classes and interfaces. Abstract classes define the blueprint of an object but cannot be instantiated. Interfaces define the behavior without providing implementation.

  • Encapsulation: Encapsulation is achieved by making the fields and methods of a class private and providing public getter and setter methods to access and modify them. This hides the internal implementation details.

  • Polymorphism: Dart supports runtime polymorphism using method overriding. A subclass can override a method of its parent class and provide its own implementation.

So yes, we can say that Dart's OOP is "a la carte" in the sense that it supports only some features of OOP - single inheritance, no multiple inheritance, no method overloading, etc. But it provides the core OOP concepts in a clean and concise syntax.


Class

What is a class? A class is a blueprint that defines the properties and behaviors that objects of that class will have.

The main purpose of a class is to represent a type of object. A class acts as a template that defines the format for objects that belong to that class.

Components of a class:

Class name: The class name acts as a label that identifies the class. The class name should be a noun that accurately describes what kind of objects the class represents.

Properties: Properties define the attributes or characteristics of the objects in that class. Properties are variables that are part of the class.

Methods: Methods define the behaviors or actions that objects of the class can perform. Methods are functions that are part of the class.

Constructor: A constructor initializes an object when it is created. It gives the object an initial state. The constructor has the same name as the class.

In summary, a class defines the state and behavior that objects of that class will have. The class name, properties, methods and constructor are the main components that make up a class.


Example

Here is an example of defining a class in Dart:

class Person {
  String name;  // Property
  int age;      // Property

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

  void greet() {  // Method
    print('Hello, my name is $name and I'm $age years old.');
  }
}

We can comment about the class as follows:

  • Person is the class name

  • name and age are properties

  • Constructor is used to initialize name and age

  • greet() method prints a greeting

We can create objects from this class like this:

var john = Person('John', 30);
var jane = Person('Jane', 25);

And call the greet() method:

john.greet();
jane.greet();

This will print:

Hello, my name is John and I'm 30 years old.
Hello, my name is Jane and I'm 25 years old.

The class defines the common properties (name and age) and behavior (greet()) for Person objects. The constructor initializes each object with a name and age.

Notes:

  1. A class name start with uppercase.

  2. The constructor method has the same name as the class name


Versus Java

Here are the main similarities and differences between defining classes in Dart vs Java:

Similarities:

  • The basic structure is similar, using the class keyword to define a class.

  • Classes contain properties (variables) and methods (functions).

  • Constructors are used to initialize objects.

Differences:

  • Dart uses keywords like void for return type, while Java uses data types like String, int etc.

  • Dart properties and methods can be initialized right in the constructor using this., while Java requires setter methods.

  • Dart makes fields optional, while Java requires defining access modifiers (public, private, protected).

  • Dart constructors can be named, while Java only allows one no-arg constructor.

  • Dart allows optional types for variables, while Java requires declaring types.

  • Dart does not have static/final keywords for fields, but uses final for constants.

Dart: for example ...

class Person {
  String name;

  Person(this.name);
}

Java: for example ...

public class Person {
  private String name;

  public Person(String name) {
    this.name = name;   
  }
}

In summary, while the basic concepts are the same, Dart's class syntax is more concise and optional compared to Java's more explicit syntax. Dart focuses on simplicity and optional typing, while Java emphasizes type safety and explicitness.


Inheritance

Here is an example of inheritance in Dart:

class Person {
  String name;
  int age;

  Person(this.name, this.age);
}

class Student extends Person {
  String id;  
  String major;

  Student(name, age, this.id, this.major) : super(name, age); 

  void study() {
    print('Studying $major');  
  }
}

Note:

  • : super, refers to the parent class

  • : super constructor calls the parent class constructor

  • It is used to initialize the parent class when inheriting from it

  • We pass it the required arguments to initialize the parent class

Without calling the super constructor, the parent class would not be initialized properly when creating a subclass object.

Class Instance

A class instance is an object. We can create a Student object:

var john = Student('John', 20, '123', 'Computer Science');

Student inherits all properties and methods from Person, and also defines its own properties (id and major) and methods (study()).

We pass the arguments to the Student constructor, which then passes them to the super constructor to initialize the base Person class.

The main things to note are:

  • Use extends keyword to inherit from a base class

  • Call the super constructor to initialize the base class

  • Student has access to all properties and methods of Person

  • Student can override methods from Person

So in summary, inheritance allows us to define a general class (Person) and then specialize it (Student) by inheriting and adding more specific properties and behaviors.


Encapsulation

Dart does not have private/public access modifiers like Java. Instead, it uses underscore (_) as a convention to mark something as "private".

When you prefix a class member with an underscore, it signals to other developers that it is an internal implementation detail and should not be accessed directly from outside the class.

For example:

class Person {
  String _name; // Indicates name is private

  int age;      // Implicitly public

  Person(this._name, this.age);
}

void main() {
  Person p = Person('John', 30);

  print(p.age); // OK
  print(p._name); // Compiler error! _name is private
}

Here name is marked as private by prefixing it with an underscore. The compiler will generate an error if you try to access name from outside the Person class.

While this is just a convention and not enforced by the compiler, it is a widely followed practice to achieve encapsulation in Dart.

The benefits of this approach are:

  • Simple and lightweight syntax

  • No need for public/private access modifiers

  • Still allows encapsulation by marking "internal" members with _

So in summary, Dart achieves encapsulation through convention (using underscores for "private" members), rather than explicit access modifiers like private/public in Java.


Interfaces

Dart uses single inheritance, meaning a class can only inherit from one superclass.

This limitation is overcome using:

Interfaces - A class can implement multiple interfaces, achieving a "multiple inheritance-like" behavior without actually inheriting from multiple superclasses.

For example:

abstract class Flyable {
  void fly(); 
}

abstract class Swimmable {
  void swim();  
}

class Duck implements Flyable, Swimmable {
  // Implement fly() and swim() 
}

Here Duck implements both the Flyable and Swimmable interfaces, even though it only inherits from the Object superclass.

Mixins - A class can inherit from one superclass and compose in multiple mixins, inheriting their behaviors.

For example:

mixin Flyable {
  void fly() => print('flying!');
}

mixin Swimmable {
   void swim() => print('swimming!'); 
}

class Duck extends Animal with Flyable, Swimmable {

}

Here Duck inherits from the Animal superclass and also composes in the Flyable and Swimmable mixins, effectively inheriting their behaviors.

In summary, Dart allows:

  • Single inheritance from one superclass

  • "Multiple inheritance-like" behavior using:

  1. Interfaces - A class can implement multiple interfaces

  2. Mixins - A class can inherit from one superclass and compose in multiple mixins.

This allows code reuse while maintaining a simple class hierarchy with single superclass inheritance.


Object

In Dart, the root class is called "Object". Every class in Dart implicitly inherits from Object.

We can use the Object class in Dart in the following ways:

  1. As the superclass for all other classes: Since every class implicitly inherits from Object, we can omit it as the superclass when declaring a class:
class Person {
  // Implicitly inherits from Object
}
  1. Using methods from the Object class: The Object class contains some useful methods that we can use in our own classes, like:
  • toString(): Returns a String representation of the object. We can override this to provide a custom String for our class.

  • hashCode: Returns a hash code for the object. We can override this to provide a custom hash code.

  • \== operator: Checks for equality between two objects. We can override this to provide a custom equality check.

For example:

class Person {
  String name;

  Person(this.name);

  @override
  String toString() {
    return 'Person($name)';  
  }
}

void main() {
  var person = Person('John');
  print(person); // Prints Person(John)
}
  1. As the superclass when we want a generic superclass: If we want a superclass that provides some generic functionality, we can extend Object:
class Identifiable extends Object {
  final int id;

  Identifiable(this.id);  
}

class Person extends Identifiable {
  String name;

  Person(id, this.name) : super(id);
}

So in summary, the Object class in Dart acts as the root superclass for all other classes. We implicitly inherit from it, and we can override some of its useful methods to provide functionality specific to our own classes.


Disclaim: This article was created with Rix (AI bot). I can't guarantee this is all you need to know about classes. Maybe there is more, check the documentation.