Menu
Coddy logo textTech

Mixin vs Interface

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

You've seen how mixins compare to inheritance. Now let's compare mixins to interfaces, since both allow you to work with multiple types. The key difference is what they provide: interfaces define a contract, while mixins provide implementation.

When you implement an interface, you promise to provide certain methods, but you write all the code yourself. When you use a mixin, you get the actual code for free.

// Interface approach - you must implement everything
abstract class Printable {
  void printInfo();
}

class Report implements Printable {
  @override
  void printInfo() {
    print('Printing report...');  // You write this
  }
}

// Mixin approach - implementation included
mixin Printable {
  void printInfo() {
    print('Printing...');  // Already written for you
  }
}

class Report with Printable {}  // Gets printInfo() automatically

So when should you use each? Use an interface when different classes need the same method signatures but different implementations. Use a mixin when classes should share the exact same behavior.

FeatureInterfaceMixin
ProvidesContract onlyContract + code
ImplementationYou write itIncluded
Keywordimplementswith
Best forDifferent behaviors, same APIShared behavior

You can even combine both approaches - use interfaces to define what a class can do, and mixins to provide common implementations where appropriate. In Dart, when combining both, always put with before implements:

class MyClass with SomeMixin implements SomeInterface {
  // with comes before implements
}
challenge icon

Challenge

Easy

Let's build a notification system that demonstrates when to use interfaces versus mixins. You'll create an interface for notifications that require different implementations, and a mixin for shared logging behavior that should be identical across all notification types.

You'll organize your code into three files:

  • notifiable.dart: Define an abstract class Notifiable that acts as an interface. It should declare two abstract methods: send(String message) and getChannel() that returns a String. Different notification types will implement these methods differently based on their specific channel.
  • logging.dart: Create a Logging mixin that provides shared behavior all notifications should have. Include a method logSent(String channel, String message) that prints [LOG] Sent via [channel]: [message] and a method logTimestamp() that prints [LOG] Timestamp recorded. This behavior is identical for all notification types, making it perfect for a mixin.
  • main.dart: Import both files and create two notification classes that combine the interface with the mixin:
    • An EmailNotification class that uses the Logging mixin and implements Notifiable. Its getChannel() should return 'Email', and its send() method should print Sending email: [message], then call logSent() with the channel and message, then call logTimestamp()
    • An SMSNotification class that uses the Logging mixin and implements Notifiable. Its getChannel() should return 'SMS', and its send() method should print Sending SMS: [message], then call logSent() with the channel and message, then call logTimestamp()
    Then create an EmailNotification and call send('Hello World'), print an empty line, create an SMSNotification and call send('Quick update')

Notice how the interface forces each class to write its own send() and getChannel() implementations (different behavior), while the mixin provides the logging methods that work identically for both (shared behavior).

Expected output:

Sending email: Hello World
[LOG] Sent via Email: Hello World
[LOG] Timestamp recorded

Sending SMS: Quick update
[LOG] Sent via SMS: Quick update
[LOG] Timestamp recorded

Cheat sheet

Interfaces define a contract (method signatures), while mixins provide implementation (actual code).

When you implement an interface, you must write all the code yourself:

abstract class Printable {
  void printInfo();
}

class Report implements Printable {
  @override
  void printInfo() {
    print('Printing report...');  // You write this
  }
}

When you use a mixin, you get the implementation for free:

mixin Printable {
  void printInfo() {
    print('Printing...');  // Already written for you
  }
}

class Report with Printable {}  // Gets printInfo() automatically

When to use each:

  • Use an interface when different classes need the same method signatures but different implementations
  • Use a mixin when classes should share the exact same behavior
FeatureInterfaceMixin
ProvidesContract onlyContract + code
ImplementationYou write itIncluded
Keywordimplementswith
Best forDifferent behaviors, same APIShared behavior

You can combine both approaches. When combining, always put with before implements:

class MyClass with SomeMixin implements SomeInterface {
  // with comes before implements
}

Try it yourself

import 'notifiable.dart';
import 'logging.dart';

// TODO: Create EmailNotification class that:
//       - uses the Logging mixin (with)
//       - implements Notifiable (implements)
//       - getChannel() returns 'Email'
//       - send() prints 'Sending email: [message]', then calls logSent(), then logTimestamp()

// TODO: Create SMSNotification class that:
//       - uses the Logging mixin (with)
//       - implements Notifiable (implements)
//       - getChannel() returns 'SMS'
//       - send() prints 'Sending SMS: [message]', then calls logSent(), then logTimestamp()

void main() {
  // TODO: Create an EmailNotification and call send('Hello World')
  
  // TODO: Print an empty line
  
  // TODO: Create an SMSNotification and call send('Quick update')
}
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