OOP: Mixins & Extension Methods
Reuse code across unrelated classes with mixins and extend existing APIs with extensions.
What Are Mixins?
Mixins are a way to reuse code across multiple, unrelated class hierarchies. Unlike inheritance, which creates an "is-a" relationship, mixins create a "can-do" relationship. They let you add specific capabilities to any class without polluting the inheritance chain.
In Dart, you define a mixin with the mixin keyword and apply it to a class using the with keyword. A class can use multiple mixins, and mixins can even be constrained to only work with certain types. This makes them incredibly flexible for cross-cutting concerns like logging, serialization, or analytics — capabilities that many unrelated classes might need.
For example, both a User class and a Product class might need logging, but they have nothing else in common. A mixin solves this cleanly without forcing them into the same inheritance hierarchy.
Mixins are powerful but can lead to confusion if overused. Follow these guidelines:
- Inheritance (extends): Use when there's a clear "is-a" relationship. A Dog is an Animal.
- Mixins (with): Use when adding capabilities that could apply to unrelated classes. A Dog can be Serializable. A Car can be Serializable too.
- Interfaces (implements): Use when you need a contract without any shared implementation.
Mixin Constraints
You can restrict a mixin to only work with certain types using on:
// This mixin can ONLY be used on classes that extend Streamable
mixin StreamAnalytics on Streamable {
void trackEvent(String event) {
print('Tracking: $event on ${streamName}');
if (isStreaming) log(event);
}
}
What Are Mixins?
Mixins allow you to add capabilities to classes without using inheritance. They solve the "deadly diamond problem" by providing reusable behavior that can be composed into any class:
mixin Logger {
String get logPrefix => '[LOG]';
void log(String message) {
print('$logPrefix $message');
}
}
mixin Timestampable {
String get timestamp => DateTime.now().toIso8601String();
void logWithTime(String message) {
print('[$timestamp] $message');
}
}
The with Keyword
Use with to mix capabilities into a class. You can combine multiple mixins:
// A service class that uses both mixins
class AnalyticsService with Logger, Timestampable {
String get logPrefix => '[ANALYTICS]';
void trackEvent(String event) {
logWithTime('Event tracked: $event');
}
}
class AuthService with Logger {
String get logPrefix => '[AUTH]';
void login(String user) {
log('User logged in: $user');
}
}
mixin Logger on SomeClass to require that only subclasses of SomeClass can use the mixin.
Extension Methods
Add methods to existing types without modifying their source code:
extension StringValidation on String {
bool get isEmail => contains('@') && contains('.');
bool get isPhoneNumber => startsWith('+') && length >= 10;
String get capitalized =>
length > 0 ? '${this[0].toUpperCase()}${substring(1)}' : this;
}
extension ListSort<T extends Comparable<T>> on List<T> {
List<T> get naturalSort => [...this]..sort();
List<T> get reversedSort => [...this]..sort((a, b) => b.compareTo(a));
}
void main() {
print('[email protected]'.isEmail); // true
print('hello'.capitalized); // Hello
var nums = [3, 1, 4, 1, 5];
print(nums.naturalSort); // [1, 1, 3, 4, 5]
print(nums.reversedSort); // [5, 4, 3, 1, 1]
}
Real-World Example: Logger Mixin for Analytics
mixin AnalyticsTracker {
void trackPageView(String page) {
print('📊 Page view: $page at ${DateTime.now()}');
}
void trackAction(String action, [Map? params]) {
print('📊 Action: $action ${params ?? ''}');
}
}
class HomeScreenService with Logger, AnalyticsTracker {
void loadContent() {
log('Loading home content...');
trackPageView('/home');
trackAction('content_loaded', {'items': 12});
}
}
class CheckoutService with Logger, AnalyticsTracker {
void processOrder(String orderId) {
log('Processing order: $orderId');
trackPageView('/checkout');
trackAction('order_submitted', {'order_id': orderId});
}
}
Summary
mixindefines reusable behavior without inheritancewithmixes one or more mixins into a classextensionadds methods to existing types without modifying them- Mixins are perfect for cross-cutting concerns: logging, analytics, serialization