Home
Dart
OOP
Flutter
Contact

OOP: Abstract Classes & Interfaces

Define contracts and build pluggable architectures with abstractions.

Abstract Classes

Abstraction is the practice of hiding complex implementation details behind a simple, well-defined interface. Abstract classes are the primary tool for this in Dart. They cannot be instantiated directly — instead, they define a contract that concrete subclasses must fulfill.

Think of an abstract class as a blueprint. It tells you what methods exist, but not how they work. Each subclass provides its own implementation. This lets you write code that depends on the abstract interface without knowing or caring about the specific implementation.

database.dart
abstract class DatabaseAdapter {
  String get name;

  // Abstract method — must be implemented
  void connect();
  void disconnect();
  void save(String key, Map<String, dynamic> data);
  Map<String, dynamic>? find(String key);

  // Concrete method — shared implementation
  void saveBatch(Map<String, Map<String, dynamic>> items) {
    items.forEach((key, data) => save(key, data));
    print('[$name] Saved ${items.length} items');
  }
}

Multiple Interfaces

Unlike some languages, Dart doesn't have a separate interface keyword. Every class implicitly defines an interface. Use implements to conform to multiple interfaces:

multiple_interfaces.dart
// Define capability interfaces
abstract class Serializable {
  Map<String, dynamic> toJson();
}

abstract class Validatable {
  bool isValid();
  List<String> errors();
}

// A class can implement BOTH interfaces
class User implements Serializable, Validatable {
  String name;
  String email;

  User(this.name, this.email);

  @override
  Map<String, dynamic> toJson() => {'name': name, 'email': email};

  @override
  bool isValid() => email.contains('@');

  @override
  List<String> errors() =>
    email.contains('@') ? [] : ['Invalid email format'];
}
💡 Interface vs Abstract Class Use abstract classes when you want to share implementation code between related classes. Use interfaces when you're defining capabilities that unrelated classes can share.

Interfaces

In Dart, every class implicitly defines an interface. Use implements to conform to any class's API:

sql_adapter.dart
class SqlAdapter implements DatabaseAdapter {
  String _connectionString;

  SqlAdapter(this._connectionString);

  @override
  String get name => 'SQLite';

  @override
  void connect() => print('Connected to SQL: $_connectionString');

  @override
  void disconnect() => print('Disconnected from SQL');

  @override
  void save(String key, Map<String, dynamic> data) {
    print('[SQL] INSERT $key: $data');
  }

  @override
  Map<String, dynamic>? find(String key) {
    print('[SQL] SELECT * WHERE key=$key');
    return {'key': key, 'source': 'sql'};
  }
}

Multiple Interfaces

One of Dart's unique features is that every class implicitly defines an interface. This means you can implement any class's API without inheriting from it. This is incredibly powerful for building flexible, decoupled architectures.

For example, you might have a DatabaseAdapter abstract class, and then use implements to create a Hive adapter that conforms to the same contract — without extending the abstract class. This lets you mix and match capabilities from multiple unrelated class hierarchies:

hive_adapter.dart
class HiveAdapter implements DatabaseAdapter {
  String _boxName;

  HiveAdapter(this._boxName);

  @override
  String get name => 'Hive';

  @override
  void connect() => print('Opened Hive box: $_boxName');

  @override
  void disconnect() => print('Closed Hive box');

  @override
  void save(String key, Map<String, dynamic> data) {
    print('[Hive] PUT $key: $data');
  }

  @override
  Map<String, dynamic>? find(String key) {
    print('[Hive] GET $key');
    return {'key': key, 'source': 'hive'};
  }
}

Real-World Example: Pluggable Database Adapter

main.dart
void main() {
  // Works with any DatabaseAdapter — no coupling
  List<DatabaseAdapter> adapters = [
    SqlAdapter('sqlite:///app.db'),
    HiveAdapter('cache_box'),
  ];

  for (var db in adapters) {
    db.connect();
    db.save('user_1', {'name': 'Alice'});
    var result = db.find('user_1');
    print('Found: $result\n');
    db.disconnect();
  }
}

Summary

  • abstract class defines contracts with optional concrete methods
  • implements requires fulfilling all methods of a class's interface
  • Dart supports multiple interface implementation
  • Abstract classes provide shared code; interfaces define pure contracts
🚀 Next Step Continue to Mixins & Extensions OOP in Flutter.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← Inheritance Next: Mixins & Extensions →