dart interfaces

Dart: Interfaces

In Dart programming, an interface is a way to set rules for classes. It defines a list of method names that a class must have, but does not include the method’s actual code. You can think of an interface as a promise a class makes to perform certain actions. This helps different classes follow the same pattern, even if they do those actions in their own unique way.

Dart does not have a special keyword for interfaces. Instead, you use abstract classes to define interfaces, although regular classes can work in some cases too. The important thing is that any class implementing an interface must override all its methods. This guarantees consistency and a clear structure throughout your code.

In this article, we will explore what interfaces are, how to create and use them in Dart, and look at fun examples to understand how they work. By the end, you will know how to write clean and organized code using interfaces the right way.

What is an Interface?

An interface is like a list of method names without any code. It tells classes what methods they must have, but does not say how those methods should work. It sets rules that classes need to follow, making sure they all have certain methods with the same names. This helps keep the code organized and clear.

In Dart, every class can act as an interface. This means any class’s method list can be used as a rule for other classes to follow. To use an interface, a class implements another class by using the implements keyword. When you implement an interface, your class must write all the methods listed in that interface, even if the original class has code inside them. This helps your class follow the rules exactly, but still lets you decide how the methods work.

Declaring an Interface

In Dart, any class can serve as an interface—you don’t need special keywords to create one. You can simply write a normal class with methods, but usually, abstract classes are used for this purpose. Other classes can then implement that class to follow its rules.

For example, a simple class Vehicle can act as an interface:

abstract class Vehicle {
  void start();
  void stop();
}

Here, Vehicle only lists method names. Other classes can implement Vehicle and write their own versions of start() and stop().

Implementing an Interface

When a class implements an interface, it must write all the methods listed in that interface. This means the class promises to follow the rules set by the interface.

Here’s a fun example: A Robot interface and a DancingRobot class that follows it.

abstract class Robot {
  void start();
  void dance();
  void stop();
}

class DancingRobot implements Robot {

  @override
  void start() {
    print("Robot powering up!");
  }

  @override
  void dance() {
    print("Robot is dancing the robot dance!");
  }

  @override
  void stop() {
    print("Robot shutting down.");
  }

}

void main() {

  DancingRobot bot = DancingRobot();

  bot.start();
  bot.dance();
  bot.stop();

}

The DancingRobot class uses the implements keyword to follow the Robot contract. This means it must provide its own versions of start(), dance() and stop(). If it skips even one method, Dart will show a compile-time error because the contract wasn’t fully followed.

The @override keyword is used to show that a method is being redefined from the interface. It’s not required, but it helps Dart (and other people reading your code) understand that you’re replacing a method from the abstract class.

Interface Inheritance

In Dart, interfaces can inherit from other interfaces (abstract classes), just like regular classes can inherit from other classes. This means one interface can extend another, adding more methods to the list that a class must implement. When a class implements the child interface, it must write all methods from both the parent and the child interfaces.

Here is an example to show how interface inheritance works:

abstract class Walker {
  void walk();
}

abstract class Runner extends Walker {
  void run();
}

class Athlete implements Runner {

  @override
  void walk() {
    print("Walking slowly...");
  }

  @override
  void run() {
    print("Running fast!");
  }

}

void main() {

  Athlete athlete = Athlete();

  athlete.walk();
  athlete.run();

}

In this example, Walker is an interface that has one method called walk(). The Runner interface extends Walker, which means it adds another method named run() on top of the walk() method. The class Athlete implements the Runner interface, so it must write its own versions of both walk() and run(). When we create an object of the Athlete class, it can perform both actions — walking and running — as required by the interfaces.

This way, interfaces build on each other, giving classes a clear set of rules to follow, step by step. It keeps your code simple and easy to understand.

Multiple Interfaces

In Dart, a class can implement more than one interface. This means it must write all the methods from all the interfaces it uses.

Here’s an example with a class that implements both Flyer and Swimmer:

abstract class Flyer {
  void fly();
}

abstract class Swimmer {
  void swim();
}

class Duck implements Flyer, Swimmer {

  @override
  void fly() {
    print("Duck is flying!");
  }

  @override
  void swim() {
    print("Duck is swimming!");
  }

}

void main() {

  Duck donald = Duck();
  donald.fly();
  donald.swim();

}

The Duck class must write both fly() and swim(), following the rules from both interfaces.

Extending and Inheriting with Interfaces

In Dart, interfaces come from classes, so you can also extend a class and implement interfaces at the same time. When you extend a class, you inherit its methods and variables. When you implement interfaces, you must write all their methods yourself.

Here’s an example:

class Vehicle {

  void start() {
    print("Vehicle is starting");
  }

}

abstract class Flyable {
  void fly();
}

class Car extends Vehicle implements Flyable {

  @override
  void fly() {
    print("Car can fly now!");
  }

}

void main() {

  Car myCar = Car();

  myCar.start(); // Inherited from Vehicle
  myCar.fly();   // Implemented from Flyable

}

The Car class inherits the start() method from Vehicle and must write the fly() method from Flyable. This way, you get ready-made code and rules to follow at the same time.

Interface and Polymorphism

In Dart, interfaces help achieve polymorphism. Polymorphism means using one interface to work with many different classes. When a class implements an interface, it promises to have certain methods. This lets you use objects of different classes in the same way if they share the same interface.

For example, if several classes implement a Painter interface with a method paint(), you can treat all those objects as Painter types. You don’t need to know the exact class; you just call paint() on each. This makes your code flexible and easy to expand.

Here’s a simple example:

abstract class Painter {
  void paint();
}

class HousePainter implements Painter {

  void paint() {
    print("Painting a house.");
  }

}

class CarPainter implements Painter {

  void paint() {
    print("Painting a car.");
  }

}

void main() {

  List<Painter> painters = [HousePainter(), CarPainter()];

  for (var painter in painters) {
    painter.paint();
  }

}

In this code, both HousePainter and CarPainter implement the Painter interface. The main function treats them as Painter objects and calls paint() on each. This is polymorphism through interfaces — many classes, one way to use them.

Real-World Use Case: Payment System

Imagine you are building a payment system. You have different ways to pay: credit card, PayPal, and bank transfer. Each payment method needs to do two important things: process the payment and generate a receipt.

To keep your code organized, you create an interface called PaymentMethod. This interface lists the methods every payment type must have, like processPayment() and generateReceipt(). Each payment method class will then implement this interface and write its own version of these methods.

This way, all payment methods follow the same rules but work differently behind the scenes. It makes the code easier to maintain and add new payment types without changing the whole system.

Here’s a simple example:

abstract class PaymentMethod {
  void processPayment(double amount);
  void generateReceipt();
}

class CreditCard implements PaymentMethod {

  @override
  void processPayment(double amount) {
    print("Processing credit card payment of \$${amount}");
  }

  @override
  void generateReceipt() {
    print("Generating credit card receipt");
  }

}

class PayPal implements PaymentMethod {

  @override
  void processPayment(double amount) {
    print("Processing PayPal payment of \$${amount}");
  }

  @override
  void generateReceipt() {
    print("Generating PayPal receipt");
  }

}

class BankTransfer implements PaymentMethod {

  @override
  void processPayment(double amount) {
    print("Processing bank transfer of \$${amount}");
  }

  @override
  void generateReceipt() {
    print("Generating bank transfer receipt");
  }

}

void main() {

  List<PaymentMethod> payments = [
    CreditCard(),
    PayPal(),
    BankTransfer()
  ];

  for (var payment in payments) {
    payment.processPayment(100.0);
    payment.generateReceipt();
  }

}

This example shows how interfaces help you build flexible and reusable code. Each payment method is separate but follows the same interface, making your payment system clear and easy to grow.

Interface vs Abstract Class

Interfaces only list method names without any code. They act as a pure contract that classes must follow by writing all the methods themselves.

Abstract classes, on the other hand, can have both method names without code and methods with code inside. This means abstract classes can share some behavior, while interfaces only define what methods must exist.

Fun Code Example: Animal Band

Let’s make two interfaces: Singer and Player. Each has its own method.

abstract class Singer {
  void sing();
}

abstract class Player {
  void playInstrument();
}

Now, the Dog class will implement both interfaces and perform:

class Dog implements Singer, Player {

  @override
  void sing() {
    print("Dog sings: Woof woof!");
  }

  @override
  void playInstrument() {
    print("Dog plays the drums!");
  }

}

In main(), the dog can both sing and play:

void main() {

  Dog dog = Dog();

  dog.sing();
  dog.playInstrument();

}

This shows how a class can follow many interfaces and do different things, just like a real animal band!

Conclusion

Interfaces create clear rules that classes must follow by defining the methods they need to have. They keep your code organized and simple to understand. Using interfaces allows different classes to work well together while keeping their own unique behaviors. This helps you build programs that are clean, flexible, and easy to expand over time.

Scroll to Top