DEV Community

Discussion on: The Strategy Pattern Exemplified in TypeScript

louy2 profile image
Yufan Lou

What I have come to realize is that composition versus inheritance is often not a decision I get to make. It is not a purely stylistic choice either.

For example, if I want to write composition instead of inheritance in TypeScript, I have to either manually delegate the default implementations, or add a dependency or make something like property-tunnel. Because the class part of TypeScript has been designed to accommodate the OOP people, which I think is important for the spread of the language. Using inheritance is the designed way, but that restricts me to one set of default implementations per class. This has led to the mixin pattern, using interface to extend multiple classes, as far as I know.

In Rust, default implementation is delegated automatically, with multiple possible traits on one struct, but specialization is still unstable, as soundness of full specialization is a hard problem. It seems the mixin pattern has similar problems.

Golang does away with inheritance and uses automatic delegation for composition. But it also does away with generics, and it remains to be seen how that would interplay with composition when Go 2.0 comes.

Meanwhile OCaml and Haskell goes higher kind (Functor) for both default implementation and specialization.

Come to really think about it, the interchange of features between OOP languages and functional languages have been going on way longer than impressions imply. OOP has a slogan "everything is an object", which really prioritizes objects in the parameters. Functional programming does not go "everything is a function", but "anything can be a parameter", which I find more inclusive and flexible.

(To quip a bit, for "everything is an X", Lisp is basically "everything is an s-expression", and only Ruby has taken "everything is an object" to a similar level of flexible power. JavaScript had similar potential but people got scared off eval (justifiably), and V8 never saw a pressing need to add proper macro.)

Pardon my rant again :P. Anyway, below I show an interface version of the Strategy pattern. In the end, the spirit is the same.

class CourseView
    constructor(public readonly course: Course) { }

    public sortingStrategy: SortingStrategy = () => ComparisonResult.Equal;

    // ...

interface SortingStrategy {
    (a: Student, b: Student): ComparisonResult;

// ...

cs101View.sortingStrategy = LastNameAscendingSortingStrategy;
cs101View.printParticipants("SORTED BY LAST NAME ASCENDING");

cs101View.sortingStrategy = SatScoreDescendingSortingStrategy;
cs101View.printParticipants("SORTED BY SAT SCORE DESCENDING");

cs101View.sortingStrategy = StudentNrAscendingSortingStrategy;
cs101View.printParticipants("SORTED BY STUDENT NR ASCENDING");

Speaking of which, I'd really appreciate if you can add to the post an example of a similarly parameterized strategy to show the full potential of the pattern. I used to show even beginners what's down the road maybe four or five steps from a basic tutorial, and have often been surprised how much they have figured out themselves towards it the next time I met them.