Stop Using Abstract Classes in Dart: Here’s Why Sealed Classes Are the Better Choice
Abstract vs. Sealed: Understanding Dart’s Class Hierarchy
Let’s first understand the basics of what an abstract or sealed class is!
Similarities:
- Instantiation: Neither abstract classes nor sealed classes can be instantiated directly.
- Method Definitions: Both can contain abstract methods (methods without a body) as well as concrete methods (methods with a body).
Key Differences:
- Abstract Classes: These can be sub-classed anywhere in the code, allowing for flexibility in their use across different libraries.
- Sealed Classes: In contrast, sealed classes can only be sub-classed within the same library.
Wait, it can be subclassed within the same library? Not just within the class like in Java? 😱
Privacy in dart is library level rather than class level
In Dart, privacy is determined at the library level rather than the class level. This means that if you prefix an identifier (such as a variable, method, or class) with an underscore “_”, it becomes private to the library in which it is declared.
When Sealed Class are the Better Choice ?
Let’s consider the Login
state, where LoginState
represents a closed set of possible states: LoginLoading
, LoginSuccess
, and LoginFailure
. No other states can exist outside this predefined set.
In the example above, we’ve used an abstract class to define these states. At this point, everything is functioning smoothly 😌
Now, suppose I want to introduce a new state, LoginRedirectState
. I can simply add a new class, LoginNewState
, that extends LoginState
, as demonstrated below:
Wait a moment! If you look closely, I neglected to include the new state in the handleLoginState
function, and surprisingly, the compiler didn't throw any errors. However, that's not the behavior we want ❌
Let’s change the abstract class to a sealed class, as shown below, and see what happens.
Hmm, it throws an error now 🤔
Error: The type 'LoginState' is not exhaustively matched by the switch cases since it doesn't match 'LoginRedirectState()'.
Exhaustive checking ensures that all cases are accounted for, making the code safer.
Value of Sealed Class
- Exhaustive Checks: The Dart compiler will enforce that all possible states are handled in the code. If a new state is added but not handled, you’ll receive a compile-time error, making your code safer and reducing bugs
- Clear Intent: By marking a class as sealed, you communicate the design intent that no additional subclasses should be created outside the defined set
This helps ensure that all possible states are handled in the code, and the switch statement (or future pattern matching features) can warn us if a new state is added but not handled.
Optimal Scenarios for Sealed Classes in Dart
State Management in an App: The sealed keyword is particularly useful when modeling a closed set of subclasses, such as states in a state management system (e.g., for a login screen). A sealed class allows you to define a finite set of possible states (e.g., loading, success, failure), ensuring that all states are handled in control flow constructs like switch statements.
So, Which Reigns Supreme? Abstract or Sealed Classes?
In conclusion, while sealed classes in Dart present an exciting and modern approach to handling class hierarchies, it’s essential to recognize that they are not inherently better than abstract classes. Each has its own merits and demerits, making them suitable for different scenarios.
Ultimately, the choice between sealed and abstract classes should be guided by the specific needs of your project, the complexity of your class hierarchy, and the design principles you wish to uphold. Understanding the strengths and weaknesses of each will empower you to make informed decisions and craft more robust, maintainable code.
Bonus Insights
Since Dart 3.0, there are specific rules regarding inheritance:-
Final Classes: Final classes can be sub-classed, but any subclass must also be declared as final
, base
, or sealed
.
Abstract Final Classes: Abstract final classes can only be subclassed within the same library (any subclass must also be declared as final
, base
, or sealed
)
Sealed Classes: Sealed classes can also only be subclassed within the same library (NO need to be declared as final
, base
, or sealed
)
Have a great day :)