Sharing behaviour: extends, implements and abstract
One class taking the fields and methods of another with extends, an abstract class saying what every kind must be able to do, and implements for a class that promises a shape without borrowing any code.
extends borrows what is already written
class Rect extends Shape makes Rect a kind of Shape. The subclass starts with everything Shape has — its fields, its methods, its getters — and may add its own. The constructor passes the values the parent needs up with super(...), as in Rect(this.width, this.height) : super("rect");
A method the subclass writes with the same name replaces the parent version; the @override line above it marks that on purpose. Everything else is inherited untouched, which is the point: a rule written once in the parent is a rule that cannot drift apart between two subclasses.
abstract: a shape with gaps in it
An abstract class cannot be made into an instance. It exists to be extended, and it is allowed to name members with no body at all: double area(); is a promise that every subclass will supply one. The parent can then use area() in a method of its own, knowing that every subclass will have it.
That is what makes the base class useful rather than merely tidy. The parent writes the shared part once, in terms of the gap, and each subclass fills the gap with its own arithmetic.
implements: a promise, no code
class Sample implements Priced borrows nothing. It promises to have every member the other type lists, and it must write each of them itself. The reward is that anything shaped like Priced can be used where a Priced is expected, whatever else that class happens to be.
That is how a program says "anything shaped like this will do". A list of the shared type holds one of each kind, and a single loop over it works for all of them, because each instance answers the same calls in its own way. Adding a third kind later means adding a class, not editing the loop.
What runs first when a subclass is built
A subclass constructor starts with its own initialiser list, then runs the parent constructor, then its body. Rect(this.width, this.height) : super("rect"); stores the two fields, sends the name up, and only then does any body run — which is why the parent part of the object is complete before the child uses it.
If the parent has no constructor that takes no arguments, the subclass must call one explicitly. That is why : super(...) appears so often: it is not decoration, it is the line that chooses which parent constructor runs.
Fields inherited from the parent are already there, with the values the parent constructor gave them. The subclass can read them, and it can override a method that uses them. It cannot reach a private parent member from another file, which is how the parent keeps its own rules.
One type, several kinds
A variable typed as the parent can hold any subclass instance. The list in the example is List<Product>, and every item in it is a Book or a Course. The loop calls label on each one and gets the version that belongs to that object, which is the behaviour the parent promised.
This is dispatch, and it happens at run time: the compiler checks that the parent has a label, and the object supplies the one that fits it. Adding a third kind means adding a class, not editing the loop.
An is check narrows the type inside the branch, so a child-only field such as pages becomes readable there. Use it when the shared type is not enough, and prefer pushing the shared behaviour into the parent when the same check keeps appearing.
An abstract parent and one subclass
abstract class Shape {
Shape(this.name);
final String name;
double area();
String get report => name + " " + area().toString();
}
class Rect extends Shape {
Rect(this.width, this.height) : super("rect");
final double width;
final double height;
@override
double area() => width * height;
}
void main() {
print(Rect(3, 4).report);
}One shape, two kinds of thing
Run it and notice that both lines come from the same loop. Then add a third class that implements Priced with a price of 100, put it in the list, and print the whole list again.
Three inheritance mistakes. Extending a class whose only constructor takes arguments without calling super(...), which the compiler refuses. Implementing an interface and forgetting one of its members, which is also a compile error rather than a silent gap. And calling a child-only method through a parent-typed variable, which is not allowed until an is check narrows it.
Try these next
- Add a third subclass with its own extra getter and call it through the shared list.
- Add a method to the abstract parent that uses
cents, and watch every subclass inherit it. - Use
isto print a child-only field for one kind and a different line for the rest.
Books and courses, one shared type
Run it and notice that the same loop prints two different labels. Then add a third kind of product, give it a price, and put it in the list before printing the total again.
Choose extends when the new class truly is a kind of the old one and wants its code. Choose implements when you only need to be usable in the same places, because a borrowed method that does not fit is worse than writing your own.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What can you not do with an abstract class?
