Archives January 2023

Project – Strategy – Strategy, Abstract Factory, and Singleton Design Patterns-2

The _items field is an ImmutableArray<string>, which makes changing the list impossible from the outside. For example, a consumer cannot pass a List<string> to the constructor, then change it later. Immutability has many advantages.

Let’s experiment with this by looking at the Consumer.API project a REST API application that uses the previous code. Next is a breakdown of the Program.cs file:

using MySortingMachine;
SortableCollection data = new(new[] {
    “Lorem”, “ipsum”, “dolor”, “sit”, “amet.”
});

The data member is the context, our sortable collection of items. Next, we look at some boilerplate code to create the application and serialize enum values as strings:

var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.Converters
        .Add(new JsonStringEnumConverter());
});
var app = builder.Build();

Finally, the last part represents the consumer of the context:

app.MapGet(“/”, () => data);
app.MapPut(“/”, (ReplaceSortStrategy sortStrategy) =>
{
    ISortStrategy strategy = sortStrategy.SortOrder == SortOrder.Ascending
        ?
new SortAscendingStrategy()
        : new SortDescendingStrategy();
    data.SetSortStrategy(strategy);
    data.Sort();
    return data;
});
app.Run();
public enum SortOrder
{
    Ascending,
    Descending
}
public record class ReplaceSortStrategy(SortOrder SortOrder);

In the preceding code, we declared the following endpoints:

  • The first endpoint returns the data object when a client sends a GET request.
  • The second endpoint allows changing the sort strategy based on the SortOrder enum when a client sends a PUT request. Once the strategy is modified, it sorts the collection and returns the sorted data.

The highlighted code represents the consumption of this implementation of the strategy pattern.

The ReplaceSortStrategy class is an input DTO. Combined with the SortOrder enum, they represent the data contract of the second endpoint.

When we run the API and request the first endpoint, it responds with the following JSON body:

[
  “Lorem”,
  “ipsum”,
  “dolor”,
  “sit”,
  “amet.”
]

As we can see, the items are in the order we set them because the code never called the Sort method. Next, let’s send the following HTTP request to the API to change the sort strategy to “descending”:

PUT https://localhost:7280/
Content-Type: application/json
{
    “sortOrder”: “Descending”
}

After the execution, the endpoint responds with the following JSON data:

[
  “sit”,
  “Lorem”,
  “ipsum”,
  “dolor”,
  “amet.”
]

As we can see from the content, the sorting algorithm worked. Afterward, the list will remain in the same order if we query the GET endpoint. Next, let’s look at this use case using a sequence diagram:

 Figure 7.3: Sequence diagram sorting the items using the “sort descending strategy”Figure 7.3: Sequence diagram sorting the items using the “sort descending strategy” 

The preceding diagram shows the Program creating a strategy and assigning it to SortableCollection using its SetSortStrategy method. Then, when the Program calls the Sort() method, the SortableCollection instance delegates the sorting computation to the underlying implementation of the ISortStrategy interface. That implementation is the SortDescendingStrategy class (the strategy) which was set by the Program at the beginning.

Sending another PUT request but specifying the Ascending sort order end up in a similar result, but the items would be sorted alphabetically.

The HTTP requests are available in the Consumer.API.http file.

From a strategy pattern perspective, the SortableCollection class (the context) is responsible for referencing and using the current strategy.