loading...
Cover image for The Strategy Pattern Exemplified in TypeScript

The Strategy Pattern Exemplified in TypeScript

lifelongthinker profile image Sebastian Originally published at blog.sebastian-felling.com ・10 min read

Design Patterns Exemplified in TypeScript (3 Part Series)

1) The State Pattern Exemplified in TypeScript 2) The Visitor Pattern Exemplified in TypeScript 3) The Strategy Pattern Exemplified in TypeScript

Source Code on GitHub | Original Article on my Blog | Follow me on Twitter: @LifeLongThinker

The Strategy Pattern is one of those design patterns that are remarkably simple in style yet efficient in function. Quite a few developers, when first getting in contact with a formal description of the pattern, realize they have been using it all along.

In this article I will introduce the strategy pattern and show its great potential for keeping your code base S.O.L.I.D. and clean. The code examples presented are implemented in TypeScript.

You can get the source code for this project on GitHub.

Implementing a Course Management System (First Approach)

Imagine you are tasked with implementing a course management system for a college or university. More specifically, your current job is to list the registered students of a certain course (student enrollment) and print it out in some form. Let's quickly glance at the key players involved here:

Our Key Players Involved

The classes could look as follows:

class Course
{
    constructor(public readonly nr: number,
                public readonly name: string,
                public readonly participants: Student[]) {}
}

class Student
{
    constructor(public readonly nr: number, 
                public readonly firstName: string, 
                public readonly lastName: string,
                public readonly satScore: number) {}
}

So far, there's nothing remarkable going on there: Courses have an identifying nr, name and a list of participants. Students have attributes such as an identifying nr, firstName, lastName and a satScore (see SAT score).

Now, printing the list of participants to an output device (we will stick with the Console here to keep things simple) is a task that should be delegated to some sort of View component. Let's create a CourseView component and have it print out sample data through a printParticipants() method:

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

    public printParticipants(): void {
        console.log(`\n\n==== ${this.course.name.toUpperCase()} ====`);
        console.log(`Nr\tFirst Name\tLast Name\tSAT Score`);

        this.course.participants.forEach(p => {
            console.log(`${p.nr}\t${p.firstName}\t\t${p.lastName}\t\t${p.satScore}`);
        })
    }
}

We will quickly run our sample app and check the results.

// create sample students
const students: Array<Student> = [
    new Student(46279, "John",      "Doe",      13.8),
    new Student(12345, "Jane",      "Doe",      16.4),
    new Student(15623, "Alex",      "Sanchez",   9.5),
    new Student(98745, "Vanessa",   "Miller",   19.1)
];

// create sample course and view
const cs101 = new Course(101, "Computer Science 101", students);
const cs101View = new CourseView(cs101Course);

// print
cs101View.printParticipants();

This will give us the following output:

==== COMPUTER SCIENCE 101 ====
Nr      First Name  Last Name       SAT Score
46279     John          Doe             13.8
12345     Jane          Doe             16.4
15623     Alex          Sanchez          9.5
98745     Vanessa     Miller            19.1

Great, the basics are in place and working. Printing the list of participants is still very rudimentary as it just returns the order of participants given in the sample data at design time. Usually any person viewing these lists has a certain informational need. In short, they would very much want the list to be sorted according to some criteria before it gets printed.

Feature Request I: Sorting the List of Participants by Last Names

Let's modify the CourseView class and have it sort the participants by their lastName (in ascending order) before printing out the results:

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

    public printParticipants(): void {
        console.log(`\n\n==== ${this.course.name?.toUpperCase()} ====`);
        console.log(`Nr\tFirst Name\tLast Name\tSAT Score`);

        // sort and loop over participants
        this.course.participants.sort(this.compareByLastNameAscending).forEach(p => {
            console.log(`${p.nr}\t${p.firstName}\t\t${p.lastName}\t\t${p.satScore}`);
        });
    }

    private compareByLastNameAscending(a: Student, b: Student): number {
        if(a.lastName < b.lastName)
        {
            return -1;
        }
        else if(a.lastName > b.lastName)
        { 
            return 1; 
        }
        else    // must be equal
        {
            return 0;
        }
    }
}

We are using the Array.sort([compareFunction]) function for sorting, which is part of the ECMAScript Language Specficiation 5.1. It accepts as an argument a universal compareFunctionthat defines the general sort order and that itself accepts two elements A and B and returns a negative number if A < B (i.e. A sorted before B), 0 if A == B (i.e. A and B equal) and a positive number if A > B (i.e. A sorted after B). In our case, such comparing is delegated to the compareByLastNameAscending(a: Student, b: Student): number method, which does exactly as it says: It compares the Students by their lastName in ascending order.

This gives us:

==== COMPUTER SCIENCE 101 ====
Nr      First Name  Last Name       SAT Score
46279     John          Doe             13.8
12345   Jane            Doe             16.4
98745     Vanessa     Miller            19.1
15623     Alex        Sanchez            9.5

Feature Request II: Sorting the List of Participants Dynamically by Two Criteria

Next, a new requirement request is thrown at you: The user now needs to sort the list dynamically according to two criteria:

  • lastName, and
  • nr (student number)

Dynamically here means that the user can pick at runtime which criterion to sort by. Ok, you think, no problem. You have sorted by one criterion, so now you simply sort by another. To allow the user to switch between the two, you will just have to add a switching mechanism that tells your view which sorting specifics to apply.

Let's get to it: Add a property sortByLastName: boolean to the CourseView class, which will act as a switch for the sorting criterion to apply. We will also add a new compare method private compareByScore(a: Student, b: Student): number to compare students by their scores. In our printParticipants() method we will then have to pick the proper compare method ( as selected by our switch) and pass it to the Array.sort() method:

class CourseView
{
    // ...

    // our new switch to define which criterion to sort by
    public sortByLastName: boolean = false;

    public printParticipants(): void {
        // ... (nothing new here)

        // pick the compare method according to switch
        var compareMethod =   this.sortByLastName 
                            ? this.compareByLastName 
                            : this.compareBySatScore;

        // sort by the compare method selected
        this.course.participants.sort(compareMethod).forEach(p => {
            console.log(`${p.nr}\t${p.firstName}\t\t${p.lastName}\t\t${p.satScore}`);
        })
    }

    private compareByLastName(a: Student, b: Student): number {
        // ... (nothing new here)
    }

    private compareBySatScore(a: Student, b: Student): number {
        if(a.satScore> b.satScore)
        {
            return -1;
        }
        else if(a.satScore< b.satScore)
        { 
            return 1; 
        }
        else    // must be equal
        {
            return 0;
        }
    }
}

Let's see how our app reacts when setting the sortByLastName switch to false and thus have our participants sorted by satScore:

==== COMPUTER SCIENCE 101 ====
Nr    First Name    Last Name       SAT Score
98745   Vanessa     Miller          19.1
12345   Jane        Doe             16.4
46279   John        Doe             13.8
15623   Alex        Sanchez          9.5

Fine, that works! But this begins to feel awkward. We have only added another minor feature and our CourseView class already beings to look bloated. If we keep adding more features, our class will soon burst at the seams. For now, it looks as though we might get away with this convoluted code arrangement.

But no! There we have it: a new feature request comes your way. This "getting away with" idea never really works out, does it?

Feature Request III: Sorting the List of Participants Dynamically by Three (or More) Criteria

The user now wants to sort the list of participants also by student nr. You're sensing a pattern here. We cannot just keep adding new switches and compare methods to accompany every new sorting feature. The sortByLastName switch is already a bad choice as it doesn't tell us anything about what happens if it is false. If we were to add another switch, our code base would disintegrate completely.

Moreover, all these compare methods bloat up our class. Certainly, our CourseView class is doing too many things at once and thus violating the Single Responsibility Principle (SRP). Now that you come to think about it, the class also doesn't adhere to the Open-Closed Principle (OCP): We cannot just add another sorting criterion without modifying the existing class structure. Our current code is definitely not closed to modification as the OCP demands.

If only there was a way to adhere to both SRP and OCP and be able to add custom sorting "strategies" without polluting our code! You think for a moment. Did you say "strategy"? You have an idea...

Strategy Pattern to the Rescue: Outsourcing the Sorting Methods

Can't we just delegate the entire sorting logic to a separate class, some kind of "strategy" that the user could pick and the view would adhere to? We could even go a step further and make it a class hierarchy so as to host an entire armada of different strategies for sorting. OurCourseView class could then receive a property that references the current sorting strategy.

This setup would serve two purposes: First, the user could switch between different sorting methods as per their preferred sort criterion. Second, by decoupling the sorting logic from the view logic, we could implement new sorting methods in the future without having to touch our view class. SRP and OCP would both be satisfied, and our code would look much cleaner!

This very idea is exactly what the Strategy Design Pattern is all about: Separate the execution of some business logic from the entity that makes use of it and give this entity a way of switching between different forms of the logic. Thus, the currently selected concrete implementation of said business logic within the entity becomes a strategy that can be changed and swapped as need arises.

As a first step, we would have to create a common base class called SortingStrategy. It could be made an abstract class and would only handle the sorting logic according to an internal compare method, the implementation of which it would delegate to its sub types. Our players and their relationships could then look something like this:

The Entity Relationships Diagram of All Our Players

Let's see how we would implement the abstract SortingStrategy base class:

abstract class SortingStrategy
{
    public sort (students: Array<Student>): Array<Student> {
        // create a copy of the student array
        // so as not to modify the existing array
        const copyOfStudents = students.slice(0);

        copyOfStudents.sort(this.compareStudents);
        return copyOfStudents;
    }

    protected abstract compareStudents(a: Student, b: Student): number;
}

One thing to note here is that within the sort logic we no longer operate on the original student array. Instead, we create a copy (Array.slice(0)) and operate on this one, so as not to have any permanent side-effects (operating on the original array would have it changed every time we call the sort method).

And now to the implementation of the different sorting strategies. We would need at least three types:

  • LastNameAscendingSortingStrategy: sorting by Student.lastName (ascending order)
  • StudentNrAscendingSortingStrategy: sorting by Student.nr (ascending order)
  • SatScoreDescendingSortingStrategy: sorting by Student.satScore (descending order)

Our classes would be implemented as follows:

class LastNameAscendingSortingStrategy extends SortingStrategy
{
    protected compareStudents(a: Student, b: Student): number {
        if(a.lastName < b.lastName)
        {
            return -1;
        }
        else if(a.lastName == b.lastName)
        { 
            return 0; 
        }
        else
        {
            return 1;
        }
    }
}

class StudentNrAscendingSortingStrategy extends SortingStrategy
{
    protected compareStudents(a: Student, b: Student): number {
        if(a.nr < b.nr)
        {
            return -1;
        }
        else if(a.nr == b.nr)
        {
            return 0; 
        }
        else
        {
            return 1;
        }
    }
}

class SatScoreDescendingSortingStrategy extends SortingStrategy
{
    protected compareStudents(a: Student, b: Student): number {
        if(a.satScore > b.satScore)
        {
            return -1;
        }
        else if(a.satScore == b.satScore)
        {
            return 0; 
        }
        else
        {
            return 1;
        }
    }
}

And our CourseView class would receive a sortingStrategy property, which would act as the new switch to give the client the ability to parameterize the desired sorting strategy. Also, the CourseView would have to sort the list of participants according the the strategy currently assigned.

class CourseView
{
    // ...

    // this is our new "switch"
    public sortingStrategy: SortingStrategy;

    public printParticipants(title: string): void {
        console.log(`\n\n==== ${this.course.name?.toUpperCase()} ====`);
        console.log(`==== ${title} ====`);
        console.log(`Nr\tFirst Name\tLast Name\tScore`);

        // retrieve the currently selected sorting strategy, 
        // then sort and iterate over participants
        this.sortParticipantsBySelectedStrategy().forEach(p => {
            console.log(`${p.nr}\t${p.firstName}\t\t${p.lastName}\t\t${p.satScore}`);
        })
    }

    private sortParticipantsBySelectedStrategy(): Student[] {
        if(!this.sortingStrategy)
        {
            return this.course.participants;
        }

        return this.sortingStrategy.sort(this.course.participants);
    }
}

That's it. Very nice! We have cleaned up our code base and separated the sorting logic into its own class hierarchy. This is a code base now that can easily be maintained and extended without touching existing class hierarchies.

Now let's see our final work in action:

// create sample students and course view
const students: Array<Student> = [
    new Student(46279, "John",      "Doe",      13.8),
    new Student(12345, "Jane",      "Doe",      16.4),
    new Student(15623, "Alex",      "Sanchez",   9.5),
    new Student(98745, "Vanessa",   "Miller",   19.1)
];
const cs101 = new Course(101, "Computer Science 101", students);
const cs101View = new CourseView(cs101);

// print "unsorted" state
cs101View.printParticipants("UNSORTED");

// sort by last name, then print
cs101View.sortingStrategy = new LastNameAscendingSortingStrategy();
cs101View.printParticipants("SORTED BY LAST NAME ASCENDING");

// sort by SAT score, then print
cs101View.sortingStrategy = new SatScoreDescendingSortingStrategy();
cs101View.printParticipants("SORTED BY SAT SCORE DESCENDING");

// sort by nr, then print
cs101View.sortingStrategy = new StudentNrAscendingSortingStrategy();
cs101View.printParticipants("SORTED BY STUDENT NR ASCENDING");

And our console would read:

==== COMPUTER SCIENCE 101 ====
==== UNSORTED ====
Nr      First Name      Last Name       SAT Score
46279     John              Doe             13.8
12345     Jane              Doe             16.4
15623   Alex                Sanchez          9.5
98745   Vanessa           Miller            19.1

==== COMPUTER SCIENCE 101 ====
==== SORTED BY LAST NAME ASCENDING ====
Nr       First Name     Last Name       SAT Score
46279    John               Doe             13.8
12345    Jane               Doe             16.4
98745    Vanessa            Miller          19.1
15623    Alex               Sanchez          9.5

==== COMPUTER SCIENCE 101 ====
==== SORTED BY SAT SCORE DESCENDING ====
Nr        First Name        Last Name     SAT Score
98745     Vanessa             Miller      19.1
12345     Jane              Doe         16.4
46279     John              Doe         13.8
15623     Alex              Sanchez      9.5

==== COMPUTER SCIENCE 101 ====
==== SORTED BY STUDENT NR ASCENDING ====
Nr       First Name     Last Name       SAT Score
12345    Jane           Doe           16.4
15623    Alex           Sanchez        9.5
46279    John           Doe           13.8
98745    Vanessa        Miller        19.1

That looks good. Our view and sorting strategies are working as expected.

One More Thing...

One more thing to make our code even more readable: Look again at the compareStudents(a: Student, b: Student): number method in our base class SortingStrategy. The number returned really isn't up to any good. All it does is tell us how to sort two Student instances: a < b (returns -1 or any negative number), a == b (returns 0) or a > b (returns 1 or any positive number). Let's try to make this a bit less subtle and more explicit. After all, TypeScript allows us to define number-based enums. We could define our own enum type and have it return those numbers behind more expressive names:

enum ComparisonResult
{
    FirstBeforeSecond = -1,
    Equal = 0,
    SecondBeforeFirst = 1
}

Our compareStudents(...) method would then have to return the new ComparisonResult enum type instead of a number directly (here only exemplified using the SatScoreDescendingSortingStrategy class):

class SatScoreDescendingSortingStrategy extends SortingStrategy
{
    protected compareStudents(a: Student, b: Student): ComparisonResult {
        if(a.satScore > b.satScore)
        {
            return ComparisonResult.FirstBeforeSecond;
        }
        else if(a.satScore == b.satScore)
        {
            return ComparisonResult.Equal; 
        }
        else
        {
            return ComparisonResult.SecondBeforeFirst;
        }
    }
}

Now, this change wraps it up perfectly! Your code base is now clean, readable and easily extendable. Great work! The Strategy Pattern has helped you a great deal in dealing with different ways of sorting the participants list.

You can get the source code for this project on GitHub.

Design Patterns Exemplified in TypeScript (3 Part Series)

1) The State Pattern Exemplified in TypeScript 2) The Visitor Pattern Exemplified in TypeScript 3) The Strategy Pattern Exemplified in TypeScript

Posted on by:

lifelongthinker profile

Sebastian

@lifelongthinker

Passionate about code, coding, craftsmanship, agile and coding challenges. I have been a developer for 23+ years, a clean coder for 10+ years. I love learning, growing, teaching + helping others grow.

Discussion

markdown guide
 

Hallo, Sebastian. Ausgezeichneter Artikel! Ein Tipp: Ihr Github Link is kaputt. I am a Brazilian Software Developer moving to Germany from October and learning German. So, tut mir leid ist mein deutsch ist falsch. Vielen Danke.

 

Vielen Dank, Rezende, für dein Lob und den Tipp.

I have updated my GitHub credentials.

I hope you are going to have a wonderful time in Germany. If you need any help settling in or with the language, drop me a line.

 

Why all the class inheritance boilerplate when you can pass in the function directly? Strategy pattern is from a time when the object-oriented obsession led to unfortunate languages not capable of passing functions as parameters. We are past that now.*

class Course
{
    constructor(public readonly nr: number,
                public readonly name: string,
                public readonly participants: Student[]) {}
}

class Student
{
    constructor(public readonly nr: number, 
                public readonly firstName: string, 
                public readonly lastName: string,
                public readonly satScore: number) {}
}

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

    public printParticipants(title: string, compareFn: CompareFn = () => 0): void {
        console.log(`\n\n==== ${this.course.name?.toUpperCase()} ====`);
        console.log(`==== ${title} ====`);
        console.log(`Nr\tFirst Name\tLast Name\tSAT Score`);

        // sort and loop over participants
        this.course.participants.sort(compareFn).forEach(p => {
            console.log(`${p.nr}\t${p.firstName}\t\t${p.lastName}\t\t${p.satScore}`);
        });
    }

}

function compareByLastNameAscending(a: Student, b: Student): ComparisonResult {
    if(a.lastName < b.lastName)
    {
        return -1;
    }
    else if(a.lastName > b.lastName)
    { 
        return 1; 
    }
    else    // must be equal
    {
        return 0;
    }
}

function compareByNrAscending(a: Student, b: Student): ComparisonResult {
    if(a.nr < b.nr)
    {
        return -1;
    }
    else if(a.nr == b.nr)
    {
        return 0; 
    }
    else
    {
        return 1;
    }
}

function compareByScoreDescending(a: Student, b: Student): number {
    if(a.satScore > b.satScore)
    {
        return -1;
    }
    else if(a.satScore == b.satScore)
    {
        return 0; 
    }
    else
    {
        return 1;
    }
}

enum ComparisonResult
{
    FirstBeforeSecond = -1,
    Equal = 0,
    SecondBeforeFirst = 1
}

type CompareFn = (a: Student, b: Student) => ComparisonResult

// create sample students
const students: Array<Student> = [
    new Student(46279, "John",      "Doe",      13.8),
    new Student(12345, "Jane",      "Doe",      16.4),
    new Student(15623, "Alex",      "Sanchez",   9.5),
    new Student(98745, "Vanessa",   "Miller",   19.1)
];

// create sample course and view
const cs101 = new Course(101, "Computer Science 101", students);
const cs101View = new CourseView(cs101);

// print
cs101View.printParticipants("UNSORTED");
cs101View.printParticipants("SORTED BY LAST NAME ASCENDING", compareByLastNameAscending);
cs101View.printParticipants("SORTED BY SAT SCORE DESCENDING", compareByScoreDescending);
cs101View.printParticipants("SORTED BY STUDENT NR ASCENDING", compareByNrAscending);

Btw, all the duplication in your compare functions are tiring too. How about

const ASCENDING = <T>(x: T, y: T) => x < y;
const DESCENDING = <T>(x: T, y: T) => x > y;

function compareBy(
    key: keyof Student,
    order: typeof ASCENDING
) {
    return (a: Student, b: Student): ComparisonResult => {
        if (a[key] === b[key]) {
            return 0;
        } else if (order(a[key], b[key])) {
            return -1;
        } else {
            return 1;
        }
    }
}

const compareByLastNameAscending = compareBy("lastName", ASCENDING);
const compareByNrAscending = compareBy("nr", ASCENDING);
const compareByScoreDescending = compareBy("satScore", DESCENDING);

  • Like, we are way past that now. Java has had functional interfaces since Java 8; C++ on top of function pointers has had lambda expression since C++11; C# has had lambda expression since 3.0.
 

Thanks for your input, Yufan. Sure you can just pass in the "meat" of our current SortingStrategy (the compare function) directly.

This is in no way an OOP vs. FP post. I'm sorry if I gave the wrong impression of OOP being the only (or the best) solution here. That was by no means intended.

The sub types of SortingStrategy here are very basic. Design pattern code examples tend to look over-engineered when they are stripped naked of all (potentially distracting) details. In a real-world scenario, though, my Strategy instances tend to be parameterized (and use inheritance). All things FP can do, too, of course.

The indirection you mention is just a theoretical discussion that holds no merit in a post like this. If your mindset is pro-FP then you might find it more suitable to use higher order programming.

Though, I must object to one point you're mentioning: Insinuating FP solves things better than OOP here ("We are past that now"), is just as pointless as making the reverse claim. There is no "object-oriented obsession" involved here.

 

My bad for coming off combative. I do wonder though, since inheritance in the example is actually an implementation of an interface, for what kind of Strategies do you feel inheritance is more convenient than interface in TypeScript? I initially thought about specialization, but with structural typing that's not a problem. Now what I have in mind is providing default implementation of extra utility functions on some minimal implementation, since TypeScript interfaces cannot contain implementations, but that seems not as related to Strategies.

Don't worry, I know what it feels like when the drive for optimization is triggered.

You are guessing correctly, I like to used base classes for default implementations and override certain logic in specialized classes (even those in specialized modules outside of "my" modules).

But this is just my personal preference for ordering and packaging code. The implementation of any interface could just as well resort to 'composition over inheritance', or the like. As long as there is consistency, I'm all fine and happy with it.

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.

 

Regarding the duplication. Please note that my article is geared towards beginning to intermediate developers that have not dealt with design patterns before (why else need an exemplification?).

Of course, I could have (and in real-world scenarios may well have) removed some more duplication until all functional variance was gone. But that would incur a heavy load on the interpretation of the code by the reader, one that I was not willing to make for an article like this.

 

I love the way you take the pattern down to the basics, avoiding tricks to shorten the code.
A good example, and a clear explanation.

The state and visitor patterns is just as good. Can't wait to see what's next :)

 

Thanks, TrasherDK, for your kind comments.

I work on these articles for quite some time, testing the material and examples when onboarding new team members. Once I am happy with the wording and overall code illustration and am certain that they will hit the idea home, I publish.

Will be back for more soon. Also, I'm planning on summing up the pattern series with a final complex scenario involved all or most of the patterns presented.

 

Thanks for this great article. It is very readable and clear

 

Thanks for your feedback, Patrick. I'm happy you found it useful.

 

I think it would be great if in this example the user could choose to sort by one or more criteria. How could we approach this scenario?