Dart 25 🎯 Implicit Interfaces and Class Implementation (implements)
Dart has no interface keyword. It does not need one. Every class defines an implicit interface made up of its public members, and the implements keyword lets any class adopt that interface without inheriting a single line of implementation. This is one of Dart’s most distinctive design choices, and it has deep consequences for how class hierarchies are structured. A class is not just a blueprint for objects. It is also a contract that other classes can satisfy—regardless of whether they share any code with it.
This chapter covers implicit interfaces and the implements keyword in full. You will learn what the implicit interface of a class actually contains, how implements differs from extends, when to reach for one over the other, and how Dart uses this mechanism to support multiple interface adoption without multiple inheritance. You will also see the patterns that the implicit interface enables—adapters, mocks, decorators—and the pitfalls that arise when a class’s interface changes and its implementers are not updated.
Key point: The implicit interface of a class includes every public instance member: methods, getters, setters, and fields. Private members are not part of the interface. A class that implements another class must provide concrete implementations of every member in that interface, but it inherits no code and no state.
Why implicit interfaces exist
The interface keyword problem. Many object-oriented languages require a separate construct for interfaces: interface in Java and C#, protocol in Swift, abstract base class in Python. This creates two parallel hierarchies—one for types that provide implementation and one for types that only declare contracts. Dart collapses these. Any class can serve as an interface, and any class can implement any number of interfaces. There is no need for a separate declaration.
The multiple inheritance problem. Dart supports single inheritance: a class can extend only one class. But real designs often need a type to satisfy multiple contracts. A User might be both Serializable and Comparable. With implicit interfaces, a class can implement both without inheriting from either. This gives Dart the flexibility of multiple interface adoption while avoiding the ambiguity of multiple implementation inheritance.
The testability problem. Testing often requires substituting a real class with a fake or mock. If the mock must extend the real class, it inherits state and behavior that may interfere with the test. With implements, the mock adopts only the interface and provides its own implementation. This is cleaner and more predictable.
The adapter problem. An adapter translates one interface into another. It implements a target interface and delegates to an adaptee. Because Dart’s implicit interfaces allow any class to be treated as an interface, an adapter can implement the target class’s interface without inheriting its implementation, delegating to a completely different object instead.
The decoupling problem. Code that depends on a concrete class is coupled to that class’s implementation. Code that depends on an interface is coupled only to the contract. Implicit interfaces let you depend on a contract without inventing a separate abstract class. You declare the interface by declaring the class, and consumers depend on the interface by implementing it or accepting it as a parameter type.
a. The implicit interface
Every class in Dart has an implicit interface. It consists of all the public instance members of the class: methods, getters, setters, and instance fields. Static members are not part of the interface. Constructors are not part of the interface. Private members are not part of the interface.
class Vehicle {
final String make;
final String model;
int _mileage = 0;
Vehicle(this.make, this.model);
void drive(int miles) => _mileage += miles;
int get mileage => _mileage;
String describe() => '$make $model';
static Vehicle create(String make, String model) =>
Vehicle(make, model);
}
The implicit interface of Vehicle contains:
make(final field, public)model(final field, public)drive(int)(method)mileage(getter)describe()(method)
It does not contain _mileage because it is private. It does not contain create because it is static. It does not contain the constructor because constructors are not part of the instance interface.
Any class can implement this interface:
class Bicycle implements Vehicle {
@override
final String make;
@override
final String model;
int _distance = 0;
Bicycle(this.make, this.model);
@override
void drive(int miles) => _distance += miles;
@override
int get mileage => _distance;
@override
String describe() => 'Bicycle: $make $model';
}
Bicycle implements Vehicle. It must provide make, model, drive, mileage, and describe. It inherits nothing. It has its own _distance field and its own implementations of the members.
Note that Bicycle is not a Vehicle in the type sense. Bicycle does not extend Vehicle, so a Bicycle instance cannot be assigned to a variable of type Vehicle. Wait—actually it can, because implements establishes a subtype relationship in Dart. A class that implements an interface is a subtype of that interface. So Vehicle v = Bicycle(...) is valid. The distinction is that Bicycle does not inherit Vehicle‘s implementation.
b. implements versus extends
The choice between implements and extends is the choice between adopting a contract and inheriting an implementation.
extends creates a subclass that inherits both the interface and the implementation. The subclass can override members but does not have to. It inherits fields and their state.
class ElectricCar extends Vehicle {
ElectricCar(super.make, super.model);
@override
String describe() => 'Electric ${super.describe()}';
}
ElectricCar inherits make, model, drive, mileage, and the concrete describe. It overrides describe to extend it.
implements creates a class that adopts the interface but inherits no implementation. Every member must be provided.
class RentalCar implements Vehicle {
@override
final String make;
@override
final String model;
@override
int mileage = 0;
RentalCar(this.make, this.model);
@override
void drive(int miles) => mileage += miles;
@override
String describe() => 'Rental: $make $model';
}
RentalCar must implement every member of Vehicle‘s interface. It cannot use super because it has no superclass relationship with Vehicle. It cannot access Vehicle‘s private members.
The distinction matters when a class has state that should not be shared. An ElectricCar should share the _mileage field with Vehicle because they represent the same concept. A RentalCar might track mileage differently—perhaps with a separate billing system—so it implements the interface without inheriting the field.
A class can do both:
class HybridCar extends Vehicle implements Comparable<HybridCar> {
HybridCar(super.make, super.model);
@override
int compareTo(HybridCar other) => make.compareTo(other.make);
}
HybridCar extends Vehicle (inheriting its implementation) and implements Comparable (adopting its contract). This is a common pattern: extend one class for implementation, implement additional interfaces for contracts.
c. Multiple interfaces and the adapter pattern
A class can implement multiple interfaces by listing them after implements, separated by commas.
abstract class Serializable {
Map<String, dynamic> toJson();
}
abstract class Comparable<T> {
int compareTo(T other);
}
class Version implements Serializable, Comparable<Version> {
final int major;
final int minor;
Version(this.major, this.minor);
@override
Map<String, dynamic> toJson() => {'major': major, 'minor': minor};
@override
int compareTo(Version other) {
final majorDiff = major - other.major;
return majorDiff != 0 ? majorDiff : minor - other.minor;
}
}
Version implements both Serializable and Comparable<Version>. It must provide toJson and compareTo. It inherits nothing from either interface.
The adapter pattern uses implicit interfaces to translate between incompatible APIs. An adapter implements the interface of the target and delegates to the adaptee.
class OldPrinter {
void printDocument(String text) => print('Old: $text');
}
class Printer {
void print(String text) => print('New: $text');
}
class PrinterAdapter implements Printer {
final OldPrinter _old;
PrinterAdapter(this._old);
@override
void print(String text) => _old.printDocument(text);
}
PrinterAdapter implements Printer‘s interface but delegates to OldPrinter. Code that expects a Printer can use the adapter, and the adapter translates the call.
The mock pattern uses implicit interfaces for testing. A mock implements the interface of the real class without inheriting its implementation.
class UserRepository {
User? findById(int id) => _database.query(id);
void save(User user) => _database.insert(user);
}
class MockUserRepository implements UserRepository {
final _users = <int, User>{};
@override
User? findById(int id) => _users[id];
@override
void save(User user) => _users[user.id] = user;
}
MockUserRepository implements UserRepository‘s interface without touching the database. Tests can pass the mock wherever a UserRepository is expected.
One subtlety: because Dart’s interfaces are implicit, adding a public member to a class automatically adds it to the interface. Any class that implements the interface must then provide the new member. This is a source of breaking changes when interfaces evolve. Keeping interfaces minimal and stable reduces this risk.
Complete Example Session
// ============================================
// PART 1: BASIC IMPLICIT INTERFACE
// ============================================
class Greeter {
String greet(String name) => 'Hello, $name';
}
class SpanishGreeter implements Greeter {
@override
String greet(String name) => 'Hola, $name';
}
// ============================================
// PART 2: INTERFACE WITH FIELDS
// ============================================
class Point {
final double x;
final double y;
Point(this.x, this.y);
double get magnitude => x * x + y * y;
}
class PolarPoint implements Point {
@override
final double x;
@override
final double y;
PolarPoint(double r, double theta)
: x = r * _cos(theta),
y = r * _sin(theta);
static double _cos(double t) => t;
static double _sin(double t) => t;
@override
double get magnitude => x * x + y * y;
}
// ============================================
// PART 3: IMPLEMENTS VERSUS EXTENDS
// ============================================
class Vehicle {
final String make;
Vehicle(this.make);
void start() => print('$make starting');
}
class Car extends Vehicle {
Car(super.make);
}
class Bicycle implements Vehicle {
@override
final String make;
Bicycle(this.make);
@override
void start() => print('$make pedaling');
}
// ============================================
// PART 4: MULTIPLE INTERFACES
// ============================================
abstract class Serializable {
String serialize();
}
abstract class Comparable<T> {
int compareTo(T other);
}
class Temperature implements Serializable, Comparable<Temperature> {
final double celsius;
Temperature(this.celsius);
@override
String serialize() => '${celsius}C';
@override
int compareTo(Temperature other) =>
celsius.compareTo(other.celsius);
}
// ============================================
// PART 5: EXTENDS AND IMPLEMENTS
// ============================================
abstract class Animal {
String get name;
}
class Pet {
final String owner;
Pet(this.owner);
}
class Dog extends Pet implements Animal {
@override
final String name;
Dog(this.name, super.owner);
}
// ============================================
// PART 6: ADAPTER PATTERN
// ============================================
class LegacyLogger {
void writeLog(String message) => print('LEGACY: $message');
}
abstract class Logger {
void log(String message);
}
class LoggerAdapter implements Logger {
final LegacyLogger _legacy;
LoggerAdapter(this._legacy);
@override
void log(String message) => _legacy.writeLog(message);
}
// ============================================
// PART 7: MOCK PATTERN
// ============================================
class EmailService {
void send(String to, String body) {
// real implementation
}
}
class MockEmailService implements EmailService {
final sent = <Map<String, String>>[];
@override
void send(String to, String body) {
sent.add({'to': to, 'body': body});
}
}
// ============================================
// PART 8: DECORATOR PATTERN
// ============================================
abstract class Coffee {
double get cost;
String get description;
}
class SimpleCoffee implements Coffee {
@override
double get cost => 2.0;
@override
String get description => 'Coffee';
}
class MilkDecorator implements Coffee {
final Coffee _inner;
MilkDecorator(this._inner);
@override
double get cost => _inner.cost + 0.5;
@override
String get description => '${_inner.description} + Milk';
}
// ============================================
// PART 9: INTERFACE WITH GETTERS AND SETTERS
// ============================================
class Account {
double _balance = 0;
double get balance => _balance;
set balance(double value) => _balance = value;
}
class SavingsAccount implements Account {
double _balance = 0;
@override
double get balance => _balance;
@override
set balance(double value) => _balance = value < 0 ? 0 : value;
}
// ============================================
// PART 10: POLYMORPHISM VIA IMPLEMENTS
// ============================================
abstract class PaymentMethod {
bool process(double amount);
}
class CreditCard implements PaymentMethod {
@override
bool process(double amount) {
print('Processing \$$amount via credit card');
return true;
}
}
class PayPal implements PaymentMethod {
@override
bool process(double amount) {
print('Processing \$$amount via PayPal');
return true;
}
}
void checkout(PaymentMethod method, double amount) {
method.process(amount);
}
The ten parts covered basic implicit interfaces, interfaces with fields, implements versus extends, multiple interfaces, combining extends and implements, the adapter pattern, the mock pattern, the decorator pattern, interfaces with getters and setters, and polymorphism through implements.
Quick Reference
Interface Composition
| Class Member | In Implicit Interface? |
|---|---|
| Public method | Yes |
| Public getter | Yes |
| Public setter | Yes |
| Public field | Yes (as getter and setter) |
| Final public field | Yes (as getter) |
| Private member | No |
| Static member | No |
| Constructor | No |
| Factory constructor | No |
extends vs implements
| Aspect | extends | implements |
|---|---|---|
| Inherits interface | Yes | Yes |
| Inherits implementation | Yes | No |
| Inherits fields | Yes | No |
Can use super | Yes | No |
| Multiple allowed | No | Yes |
| Must override members | Only abstract | All |
| Relationship | “is a” | “behaves like” |
Multiple Interface Rules
| Rule | Requirement |
|---|---|
| Syntax | implements A, B, C |
| Conflict resolution | Implement the union |
| Diamond problem | Not possible (no implementation) |
| Combined with extends | extends X implements A, B |
| Combined with with | extends X with M implements A |
Common Patterns
| Pattern | Use Case |
|---|---|
| Adapter | Translate one interface to another |
| Mock | Test double without real implementation |
| Decorator | Add behavior by wrapping |
| Strategy | Swap implementations at runtime |
| Facade | Simplify a complex subsystem |
Best Practices
✅ Do This:
// Use implements for pure contracts
class MockRepo implements Repository { } // ✅
// Use extends when sharing implementation
class Dog extends Animal { } // ✅
// Implement multiple interfaces
class Version implements Serializable, Comparable { } // ✅
// Keep interfaces minimal
abstract class Reader { String read(); } // ✅
// Use implements for test doubles
class FakeDatabase implements Database { } // ✅
// Use adapters to bridge incompatible APIs
class Adapter implements Target { } // ✅
// Combine extends and implements
class Car extends Vehicle implements Insurable { } // ✅
❌ Don’t Do This:
// Don't use implements when you need inherited state
class Child implements Parent { } // ⚠️ no shared code
// Don't add public members to widely-implemented interfaces casually
class Base {
void newMethod() { } // breaks all implementers // ⚠️
}
// Don't forget to implement all interface members
class Incomplete implements Greeter { } // ❌
// Don't use implements to circumvent inheritance restrictions
class B implements A { } // when B really is a subtype of A // ⚠️
// Don't mix interface and implementation without clear intent
class Confused implements A, B, C { } // ⚠️
// Don't use super with implements
class X implements Y {
@override
void f() => super.f(); // ❌ no superclass // ❌
}
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Missing member error | Interface changed | Implement new member |
Cannot use super | implements has no superclass | Use composition or delegation |
| Private member not in interface | Private members not included | Only public members required |
| Duplicate member conflicts | Two interfaces share a member name | Implement once satisfying both |
| Interface too large | Class has many public members | Extract a minimal interface |
| Breaking changes | Adding to a widely-implemented interface | Keep interfaces stable |
| Confusion between extends and implements | Both create subtypes | extends shares code, implements does not |
Real-World Examples
1. Repository Interface
class UserRepository {
User? findById(int id) => _db.query(id);
void save(User user) => _db.insert(user);
}
class MockUserRepository implements UserRepository {
@override
User? findById(int id) => User(id, 'mock');
@override
void save(User user) { }
}
2. Adapter for Legacy API
class LegacyApi {
Map<String, dynamic> fetchOld(String id) => {};
}
class Api implements LegacyApi {
@override
Map<String, dynamic> fetchOld(String id) => _fetch(id);
}
3. Decorator Pattern
class Logger {
void log(String msg) => print(msg);
}
class TimestampLogger implements Logger {
final Logger _inner;
TimestampLogger(this._inner);
@override
void log(String msg) => _inner.log('${DateTime.now()}: $msg');
}
4. Strategy Pattern
abstract class SortStrategy {
List<int> sort(List<int> data);
}
class QuickSort implements SortStrategy {
@override
List<int> sort(List<int> data) => data..sort();
}
5. Comparable Interface
class Person implements Comparable<Person> {
final String name;
Person(this.name);
@override
int compareTo(Person other) => name.compareTo(other.name);
}
6. Serializable Contract
class Config implements Map<String, dynamic> {
final Map<String, dynamic> _data;
Config(this._data);
@override
dynamic operator [](Object? key) => _data[key];
@override
void operator []=(String key, dynamic value) => _data[key] = value;
// ... other Map members
}
7. Test Double
class FakeClock implements Clock {
@override
DateTime now() => DateTime(2026, 1, 1);
}
8. Multiple Contracts
class Document implements Readable, Writable, Closeable {
@override
String read() => '';
@override
void write(String data) { }
@override
void close() { }
}
9. Plugin System
abstract class Plugin {
String get name;
void initialize();
}
class AnalyticsPlugin implements Plugin {
@override
String get name => 'analytics';
@override
void initialize() { }
}
10. Facade Pattern
class ComplexSubsystem {
void stepOne() { }
void stepTwo() { }
void stepThree() { }
}
class SimpleFacade implements ComplexSubsystem {
final _sub = ComplexSubsystem();
@override
void stepOne() => _sub.stepOne();
@override
void stepTwo() => _sub.stepTwo();
@override
void stepThree() => _sub.stepThree();
}
Visual
Implicit Interface Extraction
┌─────────────────────────────────────────────────────────────┐
│ CLASS AND ITS IMPLICIT INTERFACE │
│ │
│ class Vehicle { │
│ final String make; ────┐ │
│ final String model; ────┤ │
│ int _mileage = 0; ────┼─┐ │
│ Vehicle(this.make, ───┘ │ │
│ this.model); │ │
│ void drive(int miles) { } ──────┼──┐ │
│ int get mileage => ...; ──────┼──┼─┐ │
│ String describe() => ...; ──────┼──┼─┼──┐ │
│ static Vehicle create() { } ─────┘ │ │ │ │
│ } │ │ │ │
│ │ │ │ │
│ ┌─────────────────────────────────────┘ │ │ │
│ │ ┌───────────────────────────────────┘ │ │
│ │ │ ┌───────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ IMPLICIT INTERFACE OF Vehicle │ │
│ │ │ │
│ │ String get make; │ │
│ │ String get model; │ │
│ │ void drive(int miles); │ │
│ │ int get mileage; │ │
│ │ String describe(); │ │
│ └─────────────────────────────────────────┘ │
│ │
│ _mileage: private, not in interface │
│ create: static, not in interface │
│ Vehicle(): constructor, not in interface │
│ │
└─────────────────────────────────────────────────────────────┘
extends vs implements
┌─────────────────────────────────────────────────────────────┐
│ EXTENDS: INHERITS INTERFACE + IMPLEMENTATION │
│ │
│ ┌───────────────────────┐ │
│ │ Vehicle │ │
│ │ make, model │ │
│ │ drive(), describe() │ │
│ └───────────┬───────────┘ │
│ │ extends │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Car │ │
│ │ inherits: make, │ │
│ │ model, drive(), │ │
│ │ describe() │ │
│ │ can override: any │ │
│ └───────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ IMPLEMENTS: INHERITS INTERFACE ONLY │
│ │
│ ┌───────────────────────┐ │
│ │ Vehicle │ │
│ │ make, model │ │
│ │ drive(), describe() │ │
│ └───────────┬───────────┘ │
│ │ implements │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Bicycle │ │
│ │ must implement: │ │
│ │ make, model, │ │
│ │ drive(), describe()│ │
│ │ inherits: nothing │ │
│ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Multiple Interfaces
┌─────────────────────────────────────────────────────────────┐
│ IMPLEMENTING MULTIPLE INTERFACES │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Serializable │ │ Comparable │ │ Closeable │ │
│ │ │ │ │ │ │ │
│ │ serialize() │ │ compareTo() │ │ close() │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Document │ │
│ │ │ │
│ │ serialize() ✓ │ │
│ │ compareTo() ✓ │ │
│ │ close() ✓ │ │
│ └────────────────────────┘ │
│ │
│ A class can satisfy any number of interface contracts. │
│ │
└─────────────────────────────────────────────────────────────┘
Adapter Pattern
┌─────────────────────────────────────────────────────────────┐
│ ADAPTER PATTERN │
│ │
│ Client code expects: Logger interface │
│ ┌─────────────────────────────────────────┐ │
│ │ abstract class Logger { │ │
│ │ void log(String message); │ │
│ │ } │ │
│ └────────────────────┬────────────────────┘ │
│ │ implements │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ LoggerAdapter │ │
│ │ │ │
│ │ final LegacyLogger _legacy; │ │
│ │ │ │
│ │ void log(String message) { │ │
│ │ _legacy.writeLog(message); ────────┼────┐ │
│ │ } │ │ │
│ └─────────────────────────────────────────┘ │ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ LegacyLogger │ │
│ │ │ │
│ │ writeLog(String) │ │
│ └──────────────────────┘ │
│ │
│ Adapter implements the target interface and delegates │
│ to the legacy API. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Implicit interface | All public instance members |
| Private members | Not included |
| Static members | Not included |
| Constructors | Not included |
implements | Adopts interface, no implementation |
extends | Adopts interface and implementation |
| Multiple interfaces | Yes, comma-separated |
| Multiple inheritance | No |
super with implements | Not available |
| Subtype relationship | Yes, implements creates a subtype |
| Common patterns | Adapter, mock, decorator, strategy |
Key takeaways:
- Every class has an implicit interface. The interface consists of all public instance members: methods, getters, setters, and fields. Private members, static members, and constructors are not included.
implementsadopts the interface without inheriting implementation. A class that implements another must provide concrete implementations of every member in the interface. It inherits no code and no state.extendsshares both interface and implementation. A subclass inherits fields and methods. It can override members but does not have to. Useextendswhen there is a genuine “is a” relationship and code sharing makes sense.- A class can implement multiple interfaces. This is Dart’s alternative to multiple inheritance. Because interfaces have no implementation, there is no diamond problem.
implementscreates a subtype relationship. A class that implements an interface can be used wherever the interface type is expected, even though it shares no code with the interface’s declaring class.- The adapter pattern uses implicit interfaces. An adapter implements a target interface and delegates to an adaptee. This is possible because Dart classes can be implemented without being extended.
- Mocks and test doubles use
implements. A mock implements the interface of the real class without inheriting its implementation, giving tests full control over behavior. - Adding a public member to a class changes its interface. All classes that implement the interface must then provide the new member. Keeping interfaces minimal and stable reduces the impact of changes.
Remember: Dart’s implicit interface system is one of the language’s most distinctive features. It gives you the flexibility of multiple interface adoption without the complexity of multiple inheritance. Every class is a potential interface, and the implements keyword lets any class satisfy that interface without sharing code. Use extends when you want to share implementation and establish a true subtype. Use implements when you want to adopt a contract without inheriting implementation. The distinction is not just syntactic; it determines what the class inherits, what it must implement, and how it relates to the rest of the type hierarchy. Choosing correctly is the difference between a design that is flexible and one that is entangled.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!