GoF Patterns

List of Gang of Four patterns – the very patterns that can get you flunked at an interview.

Generative Patterns

Structural patterns

Patterns of behavior

Pattern Interpreter

What’s included

The Interpreter pattern is a Behavioral design pattern. This pattern allows you to implement your own programming language by working with an AST tree, the nodes of which are terminal and non-terminal expressions that implement the Interpret method, which provides the functionality of the language.

  • Terminal expression – for example, the string constant – “Hello World”
  • A non-terminal expression – for example Print(“Hello World”) – contains Print and the argument from the Terminal expression “Hello World”

What is the difference? The difference is that interpretation ends on terminal expressions, and for non-terminal expressions it continues in depth along all incoming nodes/arguments. If the AST tree consisted only of non-terminal expressions, then the application execution would never be completed, since some finiteness of any process is required, and this finiteness is represented by terminal expressions, they usually contain data, such as strings.

An example of an AST tree is below:


Dcoetzee, CC0, via Wikimedia Commons

As you can see, terminal expressions are constant and variable, non-terminal expressions are the rest.

What is not included

The implementation of the Interpreter does not include parsing the language string input into an AST tree. It is enough to implement classes of terminal, non-terminal expressions, Interpret methods with the Context argument at the input, format the AST tree from expressions, and run the Interpret method at the root expression. The context can be used to store the application state during execution.

Implementation

The pattern involves:

  • Client – ​​returns the AST tree and runs Interpret(context) for the root node (Client)
  • Context – contains the state of the application, passed to expressions during interpretation (Context)
  • Abstract expression – an abstract class containing the Interpret(context) (Expression) method
  • A terminal expression is a final expression, a descendant of an abstract expression (TerminalExpression)
  • A non-terminal expression is not a final expression, it contains pointers to nodes deep in the AST tree, subordinate nodes usually affect the result of interpreting the non-terminal expression (NonTerminalExpression)

C# Client Example

        static void Main(string[] args)
        {
            var context = new Context();
            var initialProgram = new PerformExpression(
                new IExpression[] {
                    new SetExpression("alpha", "1"),
                    new GetExpression("alpha"),
                    new PrintExpression(
                        new IExpression[] {
                            new ConstantExpression("Hello Interpreter Pattern")
                        }
                    )
                }
            );
            System.Console.WriteLine(initialProgram.interpret(context));
        }
}

Abstract Expression Example in C#

{
    String interpret(Context context);
}

Example of Terminal Expression in C# (String Constant)

{
    private String constant;

    public ConstantExpression(String constant) {
        this.constant = constant;
    }

    override public String interpret(Context context) {
        return constant;
    }
}

Example of Non-Terminal Expression in C# (Start and concatenate results of subordinate nodes, using the separator “;”

{
    public PerformExpression(IExpression[] leafs) : base(leafs) {
        this.leafs = leafs;
    }
    
    override public String interpret(Context context) {
        var output = "";
        foreach (var leaf in leafs) {
            output += leaf.interpret(context) + ";";
        }
        return output;
    }
}

Can you do it functionally?

As we know, all Turing-complete languages ​​are equivalent. Is it possible to transfer the Object-Oriented pattern to the Functional programming language?

For the experiment, we can take the FP language for the web called Elm. Elm does not have classes, but it does have Records and Types, so the following records and types are involved in the implementation:

  • Expression – an enumeration of all possible expressions of the language (Expression)
  • Subordinate expression – an expression that is subordinate to a Nonterminal expression (ExpressionLeaf)
  • Context – a record that stores the state of the application (Context)
  • Functions implementing Interpret(context) methods – all necessary functions implementing the functionality of Terminal, Non-terminal expressions
  • Auxiliary records of the Interpreter state – necessary for the correct operation of the Interpreter, store the state of the Interpreter, context

An example of a function implementing interpretation for the entire set of possible expressions in Elm:

  case input.expression of
    Constant text ->
      { 
        output = text, 
        context = input.context 
      }
    Perform leafs ->
      let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
        let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
          let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
            {
              output = (runExpressionLeaf outputExpressionInput).output,
              context = input.context
            }
    Print printExpression ->
      run 
      { 
        expression = printExpression, 
        context = input.context 
      }
    Set key value ->
      let variables = Dict.insert key value input.context.variables in
      {
        output = "OK",
        context = { variables = variables }
      }
    Get key ->
      {
        output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
        context = input.context
      }

And parse?

Parsing source code into an AST tree is not part of the Interpreter pattern, there are several approaches to parsing source code, but we’ll talk about that some other time.
In the implementation of the Interpreter for Elm, I wrote the simplest parser in the AST tree, consisting of two functions – parsing the node, parsing the subordinate nodes.

parseLeafs state =
    let tokensQueue = state.tokensQueue in
        let popped = pop state.tokensQueue in
            let tokensQueueTail = tail state.tokensQueue in
                if popped == "Nothing" then
                    state
                else if popped == "Perform(" then
                    {
                        tokensQueue = tokensQueue,
                        result = (state.result ++ [Node (parse tokensQueue)])
                    }
                else if popped == ")" then
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = state.result
                    }
                else if popped == "Set" then
                    let key = pop tokensQueueTail in
                        let value = pop (tail tokensQueueTail) in
                            parseLeafs {
                                tokensQueue = tail (tail tokensQueueTail),
                                result = (state.result ++ [Node (Set key value)])
                            }
                else if popped == "Get" then
                    let key = pop tokensQueueTail in
                        parseLeafs {
                            tokensQueue = tail tokensQueueTail,
                            result = (state.result ++ [Node (Get key)])
                        }
                else 
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = (state.result ++ [Node (Constant popped)])
                    }

parse tokensQueue =
    let popped = pop tokensQueue in
        let tokensQueueTail = tail tokensQueue in
            if popped == "Perform(" then
                Perform (
                    parseLeafs {
                        tokensQueue = tokensQueueTail, 
                        result = []
                    }
                ).result
            else if popped == "Set" then
                let key = pop tokensQueueTail in
                    let value = pop (tail tokensQueueTail) in
                        Set key value
            else if popped == "Print" then
                Print (parse tokensQueueTail)
            else
                Constant popped

Links

https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp

Sources

https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/

How I didn’t hit the guy on the pole or a story about amazing ingenuity

In this note I will write about the importance of architectural decisions in the development, support of the application, in the conditions of team development.

Professor Lucifer Gorgonzola’s Self-Operating Napkin. Rube Goldberg

In my youth, I worked on an app for ordering a taxi. In the program, you could choose a pickup point, a drop point, calculate the cost of the trip, the type of tariff, and, in fact, order a taxi. I got the application at the last stage of pre-launch, after adding several fixes, the application was released in the AppStore. Already at that stage, the whole team understood that it was implemented very poorly, design patterns were not used, all components of the system were tightly connected, in general, it could have been written into one large solid class (God object), nothing would have changed, since the classes mixed their boundaries of responsibility and, in general, overlapped each other with a dead link. Later, the management decided to write the application from scratch, using the correct architecture, which was done and the final product was implemented to several dozen B2B clients.

However, I will describe a funny incident from the previous architecture, from which I sometimes wake up in a cold sweat in the middle of the night, or suddenly remember in the middle of the day and start laughing hysterically. The thing is that I couldn’t hit the guy on the pole the first time, and this brought down most of the application, but first things first.

It was a normal working day, one of the customers gave me a task to slightly improve the design of the application – just move the icon in the center of the pickup address selection screen up a few pixels. Well, having professionally estimated the task at 10 minutes, I moved the icon up 20 pixels, completely unsuspecting, I decided to check the taxi order.

What? The app doesn’t show the order button anymore? How did that happen?

I couldn’t believe my eyes, after raising the icon by 20 pixels the app stopped showing the continue order button. After reverting the change I saw the button again. Something was wrong here. After sitting in the debugger for 20 minutes I got a little tired of unwinding the spaghetti of overlapping class calls, but I found that *moving the image really changes the logic of the app*

The whole thing was in the icon in the center – a man on a pole, when the map was moved, he jumped up to animate the camera movement, this animation was followed by the disappearance of the button at the bottom. Apparently, the program thought that the man, moved by 20 pixels, was jumping, so according to internal logic, it hid the confirmation button.

How can this happen? Does the *state* of the screen depend not on the pattern of the state machine, but on the *representation* of the position of the man on the pole?

That’s exactly what happened, every time the map was drawn, the application *visually poked* the middle of the screen and checked what was there, if there was a guy on a pole, it meant that the map shift animation had ended and the button needed to be shown. If there was no guy there, it meant that the map was shifting and the button needed to be hidden.

Everything is great in the example above, firstly it is an example of a Goldberg Machine (smart machines), secondly it is an example of a developer’s unwillingness to somehow interact with other developers in the team (try to figure it out without me), thirdly you can list all the problems with SOLID, patterns (code smells), violation of MVC and much, much more.

Try not to do this, develop in all possible directions, help your colleagues in their work. Happy New Year to all)

Links

https://ru.wikipedia.org/wiki/Goldberg_Machine

https://ru.wikipedia.org/wiki/SOLID

https://refactoring.guru/ru/refactoring/smells

https://ru.wikipedia.org/wiki/Model -View-Controller

https://refactoring.guru/ru/design-patterns/state

Pattern Facade


Facade is a structural design pattern. It provides a single interface to work with complex systems, allowing clients to not have implementation details about these systems, thus simplifying their code, implementing weak coupling between clients and lower-level systems. GoF has a good example of Facade – a compiler of programming languages, providing different clients pursuing different goals with the ability to build code through a single facade-compiler interface.

Sources

https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Abstract Factory Pattern

Abstract factory– provides an interface for creating related objects without specifying specific classes.

I really like the alternative name of this pattern – Kit

It is very similar to Factory Method, however Abstract Factories must describe the relationship between the objects being created, otherwise it is simply an anti-pattern God Object, which creates everything in a row in a haphazard manner.

Let’s imagine developing an AR framework for glasses, we display indoor navigation arrows, icons of stores, interesting places, windows and buttons with information about a place where the user is currently located on the screen.

At the same time, we need the ability to customize the appearance and behavior of AR environment controls. This is exactly the case where we need to use the Set pattern.

Let’s write the interface of the Abstract Factory and Abstract Products – parent protocols, elements of the AR environment:

protocol ARFactory {
    func arrow() -> ARArrow
    func icon() -> ARIcon
    func button() -> ARButton
    func window() -> ARWindow
}

protocol ARArrow {
    var image: { get }
    func handleSelection()
}

protocol ARIcon {
    var image: { get }
    var title: String
}

protocol ARButton {
    var title: String
    func handleSelection()
}

protocol ARWindow {
    var title: String
    var draw(canvas: Canvas)
}

Now, set developers will need to implement a Concrete Factory based on the Abstract Factory interface, and all elements will have to be implemented together; other parts of the application will be able to work with the factory without changing their code.

Sources

https://refactoring.guru/ru/design-patterns /abstract-factory
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Factory Method

The Factory Method pattern is a creational design pattern.
This pattern describes the creation of an interface for creating an object of a specific class. Seems simple, right?

In theory

Let’s say we are developing a framework for working with AR glasses. When tilting the head to the side, a menu of available applications should appear before the user’s eyes. Applications will be developed by third-party companies, clients of our framework. Naturally, we do not know which applications, icons, names should appear, so we must provide an interface for implementing the icon and related information about the application. Let’s call it Product:

protocol Product {
 var name: String { get }
 var image: Image { get }
 var executablePath: String { get }
}

Next, we need to provide an interface for our clients to implement the issuance of an array of applications of their Specific Product – an array of application icons with names, which we will already draw in the framework.

Let’s write this interface – a Creator interface containing a Factory Method that returns an array of Products.

protocol Creator {
 func factoryMethod() -> [Product]
}

In practice

The first client of our AR framework was 7B, a leading supplier of coffee maker software in Honduras. They want to sell augmented reality glasses with the ability to brew coffee, check the water/beans level, and show the way to their nearest coffee maker in indoor map mode.

They take on the development of the software, all we need to do is provide documentation on the Creator and Product interfaces, for the correct display of the list of applications and their subsequent launch.

After the documentation is transferred, the 7B company, using the Creator interface, implements the Concrete Creator class that returns an array of icon applications. The icon applications themselves are Concrete Product classes implementing the Product interface.

Example Code Specific Products:

class CoffeeMachineLocator: implements Product {
 let name = “7B Coffee Machine Locator v.3000”
 let image = Image.atPath(“images/locator.tga”)
 let executablePath = “CoffeeMachineLocator.wasm”
}

class iPuchinno: implements Product {
 let name = “iPuchinno 1.0.3”
 let image = Image.atPath(“images/puchino.pvrtc”)
 let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}

A Concrete Creator class that returns an array of two applications:

class 7BAppsCreator: implements Creator {
 func factoryMethod() -> [Product] {
  return [CoffeeMachineLocator(), iPuchinno()]
 }
}

After that, the 7B company compiles the library of Specific Products, Specific Creator and combines it with our framework, starts selling AR glasses for their coffee makers, no modifications are required on our part.

Sources

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Pattern Team

The Command pattern is a behavioral design pattern.

This is the pattern I’ve been sitting with the longest, it’s so simple it’s very complex. But personally, I find the beauty of self-study is that you have all the time in the world to explore a particular issue from all angles.

So, in GoF the applicability is described quite concisely and clearly:
Encapsulates a request as an object, allowing you to parameterize clients with different requests, use queues, log requests, and perform cancellation operations.

Now we will implement a simple version of the command from the description:

string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”

We encapsulated the request in a string class object, it can configure clients, add commands to the queue, log, cancel (using the “Snapshot” pattern)

I think this is quite enough to implement SQL queries and the like, but then the implementation details begin, different application options, the pattern code base, client roles also vary greatly, and auxiliary classes are added.

Material

The Command pattern begins with the Command protocol, which contains a single execute() method. Next comes the Specific Command and the Receiver. The CC implements the operation on the Receiver, describes the connection between the Receiver and the action. Is anything unclear? Me too, but let’s move on. The Client creates an instance of the Specific Command, associates it with the Receiver. Invoker is an object that carries out the process of launching the Command.

Now let’s try to understand it with an example, let’s say we want to update myOS on a myPhone phone, to do this we launch the myOS_Update! application, in it we press the Update Now! button, after 10 seconds the system will report a successful update.

The client in the example above is the myOS_Update! application, the Invoker is the “Update Now!” button, it runs the Specific Command of updating the system using the execute() method, which calls the Receiver – the operating system update daemon.

Example of use

Let’s say the UI of the myOS_Update! application is so good that they decided to sell it as a separate product to provide an interface for updating other operating systems. In this case, we will implement the application with support for extension via libraries, the libraries will contain implementations of Specific Commands, Receivers, we will leave static/immutable Invoker, Client, protocol Commands.

This eliminates the need to support mutable code, since our code will remain unchanged, problems can only arise during implementation on the client side, due to errors in the code of their Specific Commands and Receivers. Also, in such an implementation, there is no need to transfer the source code of the main application, that is, we have implemented the encapsulation of commands and UI interactions using the Command pattern.

Sources

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Pattern Builder

The Builder pattern belongs to a group of patterns, the existence of which is not particularly clear to me, I note the obvious redundancy of this. Belongs to the group of generative design patterns. It is used to implement a simple interface for creating complex objects.

Applicability

Simplification of the interface. It can make it easier to create an object in constructors with a large number of arguments, objectively improving the readability of the code.

Example in C++ without a builder:

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

Immutability. Using the builder, you can ensure the encapsulation of the created object until the final assembly stage. Here you need to think carefully whether the use of the pattern will save you from the high dynamics of the environment in which you work, perhaps the use of the pattern will not give anything, due to the simple lack of a culture of using encapsulation in the development team.

Interaction with components at different stages of object creation. Also, using the pattern, it is possible to provide step-by-step creation of an object when interacting with other components of the system. Most likely, this is very useful (?)

Criticism

Of course, you need to *carefully* think about whether it is worth setting up widespread use of the pattern in your project. Languages ​​with modern syntax and advanced IDEs eliminate the need to use the Builder, in terms of improving code readability (see the point about named arguments)
Was this pattern necessary in 1994, when the GoF book was published? Most likely yes, but judging by the open source code base of those years, few people used it.

Sources

https://refactoring.guru/ru/design-patterns/builder