Polygon Technology

Dart OOP_ Understanding Abstract Classes, Extends & Implements

Dart OOP: Understanding Abstract Classes, Extends & Implements

Dart OOP: Understanding Abstract Classes, Extends & Implements

Learn the fundamentals of OOP in Dart by understanding abstract classes, inheritance with extends, and interfaces with implements, with practical examples and clear explanations.
Frontend
August 25, 2026
Md. Asad Chowdhury Dipu
Dart OOP_ Understanding Abstract Classes, Extends & Implements - Banner

When learning Dart OOP for Flutter, abstract classes, extends, and implements can be confusing.

What exactly is an abstract class? And when should you use extends instead of implements?

This guide explains these Dart OOP concepts using simple language and practical Flutter examples, so you can write cleaner, reusable, and testable code.

Table of Contents

  • What Is an Abstract Class?
  • Can an Abstract Class Have Implemented Methods?
  • Why Can’t a Normal Class Have Abstract Methods?
  • Understanding Extends
  • Understanding Implements
  • Extends vs Implements
  • Which One Should You Use in Real Projects?
  • Real-World Example: Repository Pattern in Flutter
  • Summary & Best Practices
  • Next Steps

1. What Is an Abstract Class?

An abstract class acts as a blueprint for other classes. You cannot create an instance of it directly. Understanding Dart OOP becomes easier when you see how abstract classes define common behavior for related classes.

You can use an abstract class to:

  • Define methods that subclasses must implement
  • Share common behavior
  • Prevent direct instantiation

Example:

abstract class Animal {
  void makeSound();
}
You cannot instantiate it directly:
Animal animal = Animal();
Instead, create a concrete class based on it:
class Dog extends Animal {
  @override
  void makeSound() {
    print("Woof!");
  }
}
Here, Animal defines the expected behavior, while Dog provides the implementation.

2. Can an Abstract Class Have Implemented Methods?

Yes. An abstract class can contain both abstract methods and implemented methods.
abstract class Animal {
  void makeSound();

  void breathe() {
    print("Breathing...");
  }
}
A subclass can implement the abstract method while reusing the existing breathe() behavior:
class Cat extends Animal {
  @override
  void makeSound() {
    print("Meow");
  }
}
This approach is useful when multiple classes share common behavior but still need to provide their own implementations for certain methods.

3. Why Can’t a Normal Class Have Abstract Methods?

An abstract method does not provide an implementation.
For example:
class Animal {
  void makeSound();
}
If a class contains an abstract member, the class needs to be declared appropriately as an abstract class:
abstract class Animal {
  void makeSound();
}
This makes the intent clear: Animal provides a base definition rather than representing a directly instantiated implementation.

4. Understanding Extends

The extends keyword is used for inheritance.

Think of it as:

“This class is a specialized version of that class.”

For example:

class Animal {
  void eat() {
    print("Eating...");
  }
}

class Dog extends Animal {
  void bark() {
    print("Woof!");
  }
}
Now Dog has access to the inherited eat() method as well as its own bark() method.

Use Extends When You Want To:

  • Reuse existing logic
  • Build on existing behavior
  • Create a specialized version of a class

A common Flutter example is extending framework classes such as StatelessWidget.

5. Understanding Implements

The implements keyword is useful when a class needs to conform to a defined API or contract.

For example:

abstract class Animal {
  void makeSound();
}

class Bird implements Animal {
  @override
  void makeSound() {
    print("Chirp chirp");
  }
}

Here, Bird provides its own implementation of the required behavior.

This pattern is particularly useful for:

  • Defining APIs
  • Repository abstractions
  • Creating mock implementations for testing
  • Separating contracts from implementations

6. Extends vs Implements

The easiest way to understand the difference is to focus on inheritance versus contract.

Extends

With extends, a class builds on an existing class.

class Dog extends Animal {
  // Add or override behavior
}

Implements

With implements, a class conforms to the specified API and provides the required implementations.

class MockUserRepository implements UserRepository {
  // Provide the required behavior
}

Quick Comparison

Extends
Implements
Used for inheritance
Used for contracts and APIs
Useful for code reuse
Useful for independent implementations
Builds on an existing class
Defines required behavior
Common in Flutter widgets
Common in repository and testing patterns

For more details, see the Official Dart documentation on classes.

A Simple Mental Model

Is this class a specialized version of another class?

→ Consider extends.

“Does this class need to follow a defined contract?”

→ Consider implements.

“Use extends for inheritance and reuse; use implements when you need a class to conform to a defined contract.”

7. Which One Should You Use in Real Projects?

The answer depends on what you’re trying to achieve.

Use Implements for Repository Patterns

A repository abstraction can define what the application needs without coupling it to a specific data source.

abstract class UserRepository {
  Future<User> getUser(int id);
}
A concrete implementation can then provide the actual data source:
class FirebaseUserRepository implements UserRepository {
  @override
  Future<User> getUser(int id) async {
    // Fetch user from Firebase
  }
}

This separation can make the application easier to test and maintain.

Use Extends for Reusable Logic

When a class genuinely specializes an existing class and benefits from its behavior, inheritance can be appropriate.

For example:

class CustomButton extends StatelessWidget {
  // Override build()
}

8. Real-World Example: Repository Pattern in Flutter

Let’s put the idea together.

Step 1: Define the Contract

abstract class UserRepository {
  Future<User> getUser(int id);
}

Step 2: Create the Implementation

class FirebaseUserRepository implements UserRepository {
  @override
  Future<User> getUser(int id) async {
    return User(
      id: id,
      name: "John Doe",
    );
  }
}

Step 3: Create a Mock for Testing

class MockUserRepository implements UserRepository {
  @override
  Future<User> getUser(int id) async {
    return User(
      id: id,
      name: "Mock User",
    );
  }
}

This structure separates the contract from the implementation.

It can help make your code:

  • Testable
  • Modular
  • Easier to change

9. Summary & Best Practices

Keep these three ideas in mind:

Abstract Class

Use an abstract class when you need a base abstraction or contract.

Extends

Use extends when you need inheritance and reusable behavior.

Implements

Use implements when a class needs to follow a defined contract.

Quick Decision Guide

Need a shared base implementation?

→ Consider extends.

Need a contract between components?

→ Consider implements.

Need testable alternative implementations?

→ Consider an abstract repository contract with separate implementations.

10. Next Steps

Try these concepts in your next Flutter project:

  • Create a repository abstraction
  • Implement it with a real data source
  • Create a mock implementation for testing
  • Explore dependency injection with tools such as get_it or Riverpod
  • Write unit tests against the abstraction

Conclusion

Build Better Flutter Applications

Understanding Dart OOP is one step toward building maintainable software.

Need help building scalable digital products? Talk to our engineering team to explore how we can help.