How to define interfaces in Dart?

Learn how to define interfaces in dart? with practical examples, diagrams, and best practices. Covers oop, inheritance, dart development techniques with visual explanations.

Mastering Interfaces in Dart: A Comprehensive Guide

Hero image for How to define interfaces in Dart?

Explore how Dart leverages implicit interfaces and the implements keyword to achieve polymorphism and flexible code design, enhancing your object-oriented programming skills.

In object-oriented programming (OOP), interfaces define a contract that classes must adhere to. They specify a set of methods and properties that a class must implement, without providing the implementation details themselves. Dart, unlike some other languages, doesn't have a dedicated interface keyword. Instead, it uses an implicit interface mechanism, where every class implicitly defines an interface. This guide will walk you through how to define and use interfaces effectively in Dart, enabling you to write more modular, testable, and maintainable code.

Understanding Dart's Implicit Interfaces

In Dart, every class implicitly defines an interface containing all its instance members (methods, getters, and setters). This means you can implement any class as an interface. When a class A implements class B, it must provide concrete implementations for all the instance members declared in B. This powerful feature allows for flexible code design and promotes polymorphism without the need for a separate interface declaration syntax.

class Logger {
  void log(String message) {
    print('Log: $message');
  }
}

class ConsoleLogger implements Logger {
  @override
  void log(String message) {
    print('Console Log: $message');
  }
}

void main() {
  Logger myLogger = ConsoleLogger();
  myLogger.log('This is a test message.');
}

Demonstrates a class implementing another class's implicit interface.

Defining Explicit Interface Contracts with Abstract Classes

For clearer interface definitions, especially when you want to ensure that a class cannot be instantiated directly but only implemented, you can use abstract classes. An abstract class can define abstract methods (methods without a body) which must be implemented by any concrete class that implements or extends it. This provides a strong contract for implementers.

abstract class Shape {
  void draw(); // Abstract method
  double getArea(); // Abstract getter
}

class Circle implements Shape {
  double radius;

  Circle(this.radius);

  @override
  void draw() {
    print('Drawing a circle with radius $radius');
  }

  @override
  double getArea() {
    return 3.14 * radius * radius;
  }
}

class Rectangle implements Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  void draw() {
    print('Drawing a rectangle with width $width and height $height');
  }

  @override
  double getArea() {
    return width * height;
  }
}

void main() {
  List<Shape> shapes = [Circle(5), Rectangle(4, 6)];

  for (var shape in shapes) {
    shape.draw();
    print('Area: ${shape.getArea()}');
  }
}

Using an abstract class to define a clear interface contract.

classDiagram
    direction LR
    class Shape {
        <<abstract>>
        +void draw()
        +double getArea()
    }
    class Circle {
        +double radius
        +Circle(radius)
        +void draw()
        +double getArea()
    }
    class Rectangle {
        +double width
        +double height
        +Rectangle(width, height)
        +void draw()
        +double getArea()
    }
    Circle --|> Shape : implements
    Rectangle --|> Shape : implements

Class diagram illustrating the Shape interface implemented by Circle and Rectangle.

Multiple Interfaces and Mixins

Dart allows a class to implement multiple interfaces, enabling it to adhere to several contracts simultaneously. This is a powerful way to achieve multiple inheritance of types, without the complexities of multiple inheritance of implementation. Additionally, Dart's mixin feature can be used to reuse code across different class hierarchies, often complementing interface usage by providing default implementations for certain behaviors.

abstract class Flyable {
  void fly();
}

abstract class Swimmable {
  void swim();
}

class Duck implements Flyable, Swimmable {
  @override
  void fly() {
    print('Duck is flying.');
  }

  @override
  void swim() {
    print('Duck is swimming.');
  }
}

void main() {
  Duck myDuck = Duck();
  myDuck.fly();
  myDuck.swim();
}

A class implementing multiple interfaces.