Inheritance is not a banned practice. It should just be your second choice when there's a better path through composition. Do you see one here? Interfacing to address Liskov's substitution is a perfectly valid reason to "extend" in many older languages, since their inheritance and interface mechanism are conflated. The way it's done here is fine. Single parent, shallow and only for the purpose of overriding and specializing.
Also, the real issue SM is trying to address is actually single responsibility and open-close, which aren't just an OO thing.
As you'll design your own libraries' functional APIs, you'll have to decide whether to publish fewer functions, with a rich set of behaviors controlled through the passing of (many) parameters (and conditionals in the function body); or take a finer grain approach with multiple functions that abide as much as possible to single responsibility and only take few input about the state. I'd bet that the former will quickly raise complaints, by both maintainers and users alike, because of all the ifs and buts typically associated with it.
For the same reasons, you don't want your methods to have divergent behavior based on state. You want multiple types.
> Inheritance is not a banned practice.
Maybe it should be. Go and Rust do not provide implementation inheritance, and I think that's for the best. Few language features have led to so much spaghetti code.
> It should just be your second choice when there's a better path through composition. Do you see one here?
I don't think this logic should be split over a graph of objects at all. This is highly cohesive code; it shouldn't be factored apart. If Metz made it clear that this was an example just for the purposes of illustration, that'd be one thing. However, the stance taken is "this is good code, and you should try to write all your code like this." It's not, though, and you shouldn't.