In Dart 3, what do the `base`, `interface`, `final` and `sealed` class modifiers each allow code in another library to do?
answer
- two capabilities: extend, implement
- base keeps implementation inherited
- interface keeps implementation private
- final closes both doors
- sealed: final plus abstract, known subtypes
basics
~20 sOutside the declaring library, base allows extending but not implementing, interface allows implementing but not extending, final allows neither, and sealed allows neither and is implicitly abstract so its same-library subtypes form a closed set.
solid answer
~40 sDart classes offer two subtyping doors to other libraries: `extends` (inherit the code) and `implements` (reuse only the type). The Dart 3 modifiers close them selectively. `base` allows `extends` but forbids `implements`, so every instance really runs your implementation, private members included. `interface` allows `implements` but forbids `extends`, so no one overrides methods your own code calls on `this`. `final` forbids both, leaving outside code only construction. `sealed` forbids both too, is implicitly `abstract`, and requires every direct subtype to live in the same library, which is what lets the compiler treat the subtypes as a closed, known set. All four restrictions are ignored inside the declaring library.
code
dart · 27 lines// payments.dart
base class PaymentMethod {
void _audit() {}
void charge(int cents) => _audit();
}
interface class PaymentGateway {
void submit(int cents) {}
}
final class Receipt {
Receipt(this.id);
final String id;
}
// app.dart
import 'payments.dart';
base class GiftCard extends PaymentMethod {} // OK: base may be extended
// class FakeMethod implements PaymentMethod {} // error: base can't be implemented outside
class StubGateway implements PaymentGateway { // OK: interface may be implemented
@override
void submit(int cents) {}
}
// class LoggingGateway extends PaymentGateway {} // error: interface can't be extended outside
// class MyReceipt extends Receipt {} // error: final can't be extended outside
final receipt = Receipt('r-1'); // OK: final can still be constructedgo deeper
Recall the two doors, extends and implements, and which one each of base, interface and final closes for other libraries.
Explain the guarantee each modifier buys: base for inherited private members and constructors, interface against fragile base classes, sealed for a closed subtype set.
Pick modifiers for a published package deliberately, knowing that adding one later is breaking and that base and final restrictions propagate to subtypes.
Set package-wide policy on which exported types are open, interface-only or closed, balancing extensibility for users against freedom to evolve the API.
## The two doors every Dart class opens by default A plain Dart `class` can be used by any other library in four ways: **constructed**, **extended** (`extends`), **implemented** (`implements`, because every class has an implicit interface), and, if declared `mixin class`, **mixed in**. Dart 3.0 added modifiers that let a library author close the subtyping doors to *other libraries* while keeping full freedom inside the declaring library. They require a library language version of at least 3.0. Picture a payments package that exports `PaymentMethod` with subclasses `Card`, `Wallet` and `BankTransfer`. Which modifier you put on `PaymentMethod` decides what an app or another package may do with it. ## What each modifier allows outside the library | Declaration | Construct | Extend | Implement | Mix in | |---|---|---|---|---| | `class` | yes | yes | yes | no | | `base class` | yes | yes | no | no | | `interface class` | yes | no | yes | no | | `final class` | yes | no | no | no | | `sealed class` | no | no | no | no | (From the dart.dev class modifier reference; `abstract` removes construction from any row.) ## `base`: you must inherit my code `base` forbids `implements` from other libraries. The guarantees dart.dev lists: - the base class **constructor always runs** for any subtype instance; - **private members** declared in the class exist in every subtype, so code like `a._validate()` cannot fail at runtime on a foreign implementation; - adding a new concrete member does not break subtypes, since they inherit it. The price is **transitivity**: every subtype, direct or indirect, must itself be `base`, `final` or `sealed`. ## `interface`: you may implement, not inherit `interface` forbids `extends` from other libraries. It protects against the **fragile base class problem**: when one of your methods calls another on `this`, you know it runs your implementation, not a foreign override. The common form is `abstract interface class PaymentGateway { ... }`, a **pure interface** with abstract members that outside code implements. ## `final`: construct only `final` combines both restrictions: outside libraries cannot extend, implement, mix in or use it in an `on` clause. They can still construct it unless it is also `abstract`. It gives the maintainer the most freedom: you can add methods or turn a constructor into a factory without breaking anyone. Like `base`, it is transitive for subtypes declared in your own library. ## `sealed`: a closed family `sealed` also forbids extending, implementing and mixing in from other libraries, and adds two rules: 1. The sealed class is **implicitly abstract**: it cannot be constructed, though it can declare factory constructors and constructors for its subclasses. 2. Every **direct subtype** must be declared in the **same library**. Because the compiler can then see every direct subtype, it can check a switch over a `PaymentMethod` for exhaustiveness (the switch mechanics are a separate topic). Unlike `base` and `final`, `sealed` is **not transitive**: a public, unmodified subclass such as `Card` can still be extended elsewhere unless you mark it too. ## Real SDK examples `dart:core` in Dart 3.13 uses all of these: `sealed class num` (with `int` and `double` as its subtypes), `abstract final class String`, `abstract final class int`, and `abstract interface class List<E>` and `Map<K, V>`. `dart:io`'s `FileSystemEvent` became `sealed` in Dart 3.1. ## Inside the declaring library All four restrictions apply only to **other libraries** — which, in Dart, means other files, even in your own package. Within the declaring library you may extend a `final` or `interface` class and implement a `base` class, subject to the transitivity rule for `base` and `final` subtypes. ## Applying them to an existing API Adding any of these modifiers to an already published class takes a capability away from users, so dart.dev's guidance for package authors is to treat it as a **breaking change** and bump the major version.
- In Dart 3, why does base protect code that calls private members on a parameter?A library-private member such as `_audit()` cannot be implemented from another library. If outside code could `implements PaymentMethod`, a call to `method._audit()` inside your library would hit an object with no such member and fail at runtime. `base` forbids outside `implements`, so every instance inherits your real `_audit`.
- In Dart 3, can code in another library construct a sealed class or an interface class?An `interface class` can be constructed anywhere unless it is also `abstract`. A `sealed class` never can: it is implicitly abstract, though it may declare factory constructors that return one of its subclasses.
- In Dart 3, is putting final on an already published class a breaking change?Yes. Any user who extended or implemented it now gets a compile error, so dart.dev advises a major version bump for adding `final`, `base`, `interface` or `sealed` to an existing public class.
saying these in an interview costs you the question
- final on a class means its fields are immutable.
- interface class declares a type with no method bodies allowed.
- base prevents other libraries from extending the class.
- sealed classes can be constructed like final classes.
- The modifiers also restrict subtyping inside the declaring library.