Dart Interface Class

Summary: In this tutorial, you’ll learn how to use the Dart interface class to create contracts that other classes must adhere to.

What is an interface class? #

In Dart, an interface is a contract between classes. Additionally, every class is an implicit interface.

Dart 3 introduced an interface modifier that allows you to define a contract between classes.

When you define a class with the interface modifier, you create a contract that other classes must implement, but cannot extend.

Here’s the basic syntax for defining an interface class:

abstract interface class InterfaceName {
    methodName1();
    methodName2();
}Code language: PHP (php)

In this syntax:

  • First, specify the interface name after the interface class keywords. If you don’t provide implementations for methods in the interface, you need to mark it as abstract.
  • Second, define methods for interfaces. These methods may have implementation or not, depending on whether abstract modifier is available.

If a class wants to use the InterfaceName, it must implement all the methods of the interface, including methodName1 and methodName2.

For example, the following defines an interface called Logger:

abstract interface class Logger {
  void log(String message);
}


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

void main() {
  Logger logger = ConsoleLogger();
  logger.log('Hi');
}Code language: JavaScript (javascript)

Output:

Console: HiCode language: HTTP (http)

Interface with Method Implementation #

The following example redefines the Logger interface with method implementation:

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

class FileLogger implements Logger {
  @override
  void log(String message) {
    print('Writing to file: $message'); // Must still override
  }
}

void main() {
  Logger logger = FileLogger();
  logger.log('Hi');
}Code language: JavaScript (javascript)

Output:

Writing to file: Hi

How it works.

First, define the Logger interface that has a method log() with an implementation:

interface class Logger {
  void log(String message) {
    print('Default log: $message');
  }
}Code language: JavaScript (javascript)

Second, define the FileLogger class that implements the Logger interface:

class FileLogger implements Logger {
  @override
  void log(String message) {
    print('Writing to file: $message'); // Must still override
  }
}Code language: PHP (php)

Third, create a FileLogger instance and call the log() method in the main() function:

void main() {
  Logger logger = FileLogger();
  logger.log('Hi');
}Code language: JavaScript (javascript)

Dart Interface class example #

Let’s model a payment system using interface class:

abstract interface class PaymentGateway {
  Future<void> charge(double amount);
}


class StripePayment implements PaymentGateway {
  @override
  Future<void> charge(double amount) async {
    print('Charging \$${amount} using Stripe...');
    await Future.delayed(Duration(seconds: 1));
    print('Payment complete.');
  }
}


class PayPalPayment implements PaymentGateway {
  @override
  Future<void> charge(double amount) async {
    print('Charging \$${amount} using PayPal...');
    await Future.delayed(Duration(seconds: 1));
    print('Payment complete.');
  }
}


enum Gateway {
  stripe,
  paypal
}


class PaymentGatewayFactory {
  
  static PaymentGateway create(Gateway name) {
    
    switch(name) {
      case Gateway.stripe: return StripePayment();
      case Gateway.paypal: return PayPalPayment();
    }
  }
}

void main() {

  var pg = PaymentGatewayFactory.create(Gateway.stripe);
  pg.charge(10.0);
  
}Code language: PHP (php)

How it works.

Step 1. Define a PaymentGateway interface with one method charge():

abstract interface class PaymentGateway {
  Future<void> charge(double amount);
}Code language: JavaScript (javascript)

Step 2. Implement the PaymentGateway using Stripe API (simulated only):

class StripePayment implements PaymentGateway {
  @override
  Future<void> charge(double amount) async {
    print('Charging \$${amount} using Stripe...');
    await Future.delayed(Duration(seconds: 1));
    print('Payment complete.');
  }
}Code language: PHP (php)

Step 3. Implement the PaymentGateway using PayPal API (simulated only):

class PayPalPayment implements PaymentGateway {
  @override
  Future<void> charge(double amount) async {
    print('Charging \$${amount} using PayPal...');
    await Future.delayed(Duration(seconds: 1));
    print('Payment complete.');
  }
}Code language: PHP (php)

Step 4. Define an enum that includes the payment gateway name:

enum Gateway {
  stripe,
  paypal
}

Step 5. Define a class that creates an instance of a class that implements the PaymentGateway interface:

class PaymentGatewayFactory {
  static PaymentGateway create(Gateway name) {
    switch(name) {
      case Gateway.stripe: return StripePayment();
      case Gateway.paypal: return PayPalPayment();
    }
  }
}Code language: JavaScript (javascript)

Step 6. Create a PaymentGateway instance and call the charge() method:

void main() {
  var pg = PaymentGatewayFactory.create(Gateway.stripe);
  pg.charge(10.0);
  
}Code language: JavaScript (javascript)

In practice, you can read the gateway name from a configuration file and dynamically swap between the payment gateways.

If you want to add payment gateway, you can:

  • First, define a class that implements the PaymentGateway interface.
  • Second, add the payment gateway name to the enum.
  • Third, swap it in the main() method. If you use a configuration, you don’t need to change the main() method.

Summary #

  • Use an interface class to define a contract between classes.
  • Classes can implement an interface class, but cannot extend it.
Was this tutorial helpful ?