Menu
Coddy logo textTech

Implicit Interfaces

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 46 of 110.

Since every Dart class automatically defines an interface, you can use any class as an interface - even concrete classes with full implementations. This is what we call an implicit interface.

Consider a class with properties and methods:

class Logger {
  String prefix = '[LOG]';
  
  void log(String message) {
    print('$prefix $message');
  }
  
  void clear() {
    print('Clearing logs...');
  }
}

When another class implements Logger, it must provide its own implementation for everything - including the prefix property:

class FileLogger implements Logger {
  @override
  String prefix = '[FILE]';
  
  @override
  void log(String message) {
    print('$prefix Writing to file: $message');
  }
  
  @override
  void clear() {
    print('Deleting log file...');
  }
}

This is the key insight: when you use implements, you're saying "my class will have the same shape" - the same methods and properties. You don't inherit any code, constructors, or default values. You're only promising to match the interface's structure.

This makes implicit interfaces powerful for creating interchangeable components. Code that expects a Logger can work with any class that implements its interface, regardless of how that implementation works internally.

challenge icon

Challenge

Easy

Let's build a notification system that demonstrates how concrete classes can serve as implicit interfaces. You'll create a base notification class and then implement it in a completely different way, showing that implements requires you to provide all members yourself.

You'll organize your code into two files:

  • notification.dart: Define your notification classes here:
    • A Notification class with a String channel property set to 'default', a method send(String message) that prints [channel]: [message], and a method getStatus() that returns the string 'Notification ready'
    • An EmailNotification class that implements Notification. Since you're using implements, you must provide your own channel property (set it to 'email'), your own send() method that prints Sending email: [message], and your own getStatus() method that returns 'Email service active'
    • A PushNotification class that implements Notification. Set its channel to 'push', make send() print Push alert: [message], and have getStatus() return 'Push service connected'
  • main.dart: Import your notification file and demonstrate how different classes can implement the same implicit interface with completely different behaviors:
    • Create an EmailNotification and a PushNotification
    • For each notification, print its status using getStatus(), then call send() with the message 'Hello World'
    • Print an empty line between the two notifications

Remember: when you use implements, you're not inheriting any code from Notification. You're only promising that your class will have the same shape - the same properties and methods. Each implementing class provides its own complete implementation.

Expected output:

Email service active
Sending email: Hello World

Push service connected
Push alert: Hello World

Cheat sheet

In Dart, every class automatically defines an implicit interface. This means you can use any class as an interface, even concrete classes with full implementations.

When a class uses implements, it must provide its own implementation for all properties and methods from the interface. You don't inherit any code, constructors, or default values - you only promise to match the interface's structure.

Example of a concrete class:

class Logger {
  String prefix = '[LOG]';
  
  void log(String message) {
    print('$prefix $message');
  }
  
  void clear() {
    print('Clearing logs...');
  }
}

Implementing the implicit interface:

class FileLogger implements Logger {
  @override
  String prefix = '[FILE]';
  
  @override
  void log(String message) {
    print('$prefix Writing to file: $message');
  }
  
  @override
  void clear() {
    print('Deleting log file...');
  }
}

This makes implicit interfaces powerful for creating interchangeable components. Code that expects a Logger can work with any class that implements its interface, regardless of internal implementation.

Try it yourself

import 'notification.dart';

void main() {
  // TODO: Create an EmailNotification instance
  
  // TODO: Print the email notification's status using getStatus()
  
  // TODO: Send a message 'Hello World' using the email notification
  
  // TODO: Print an empty line
  
  // TODO: Create a PushNotification instance
  
  // TODO: Print the push notification's status using getStatus()
  
  // TODO: Send a message 'Hello World' using the push notification
  
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming