Builder Pattern

The Builder pattern belongs to a group of patterns whose existence is not particularly clear to me, I note a clear redundancy of this. Refers to a group of generative design patterns. Used to implement a simple interface for creating complex objects.

Usage

Simplification of the interface. It can facilitate the creation of an object in constructors with a large number of arguments, objectively improve the readability of the code.

C ++ example without a builder:


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

C ++ example with a builder:


auto monster = new MonsterBuilder()
              .addWeapon(“Claws”)
              .addHealth(100)
              .build();

However, in languages ​​that support named arguments, there is no need to use it for this particular case.

Swift example using 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 stage of assembly. Here you need to think carefully about 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 work, due to the simple lack of culture of using encapsulation by the development team.

Interaction with components at different stages of creating an object. 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 it is very useful (?)

Critics

Of course you need to * thoroughly * think about whether it is necessary to establish the widespread use of the pattern in your project. Languages ​​with modern syntax and an advanced IDE eliminate the need to use the Builder in terms of improving the readability of the code (see paragraph on named arguments)
Was it necessary to use this pattern in 1994, at the time of the release of the GoF book? Most likely yes, however, judging by the Open source code base of those years, few people used it.

References

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

Composite Pattern

The Composite pattern refers to structural design patterns.
Suppose we are developing an application – a photo album. The user can create folders, add photos there, and perform other manipulations. You definitely need the ability to show the number of files in folders, the total number of all files and folders.
Obviously, you need to use a tree, but how to implement the architecture of the tree, with a simple and convenient interface? The Composite pattern comes to the rescue.

Sheila in Moonducks

Next, in the Directory, we implement the dataCount () method – by passing through all the elements lying in the component array, adding all their dataCount’s.
Done!
Go example:

package main

import "fmt"

type component interface {
    
    dataCount() int
    
}

type file struct {
    
}

type directory struct {

    c []component

}

func (f file) dataCount() int {

    return 1

}

func (d directory) dataCount() int {
    
    var outputDataCount int = 0
    
    for _, v := range d.c {
        outputDataCount += v.dataCount()
    }
    
    return outputDataCount
    
}

func (d *directory) addComponent(c component) {

    d.c = append(d.c, c)

}

func main() {
    
    var f file
    var rd directory
    rd.addComponent(f)
    rd.addComponent(f)
    rd.addComponent(f)
    rd.addComponent(f)

    fmt.Println(rd.dataCount())
    
    var sd directory
    sd.addComponent(f)
 
    rd.addComponent(sd)
    rd.addComponent(sd)
    rd.addComponent(sd)
    
    fmt.Println(sd.dataCount())
    fmt.Println(rd.dataCount())

}

References

https://refactoring.guru/design-patterns/composite

Adapter Pattern

Benjamín Núñez González

An adapter pattern refers to structural design patterns.

The adapter provides data / interface conversion between two classes / interfaces.

Suppose we are developing a system for determining the goal of a buyer in a store based on neural networks. The system receives a video stream from the store’s camera, determines customers according to their behavior, classifies them into groups. Types of groups – came to buy (potential buyer), just look (onlooker), came to steal something (thief), came to hand over goods (disgruntled buyer), came drunk / high (potential rowdy).

Like all experienced developers, we find a ready-made neural network that can classify species of monkeys in a cage according to the video stream, which the zoological institute of the Berlin zoo has kindly laid out for free access, retrain it on the video stream from the store and get a working state-of-the-art system.

There is only a small problem – the video stream is encoded in mpeg2 format, and our system only supports OGG Theora. We do not have the source code of the system, the only thing we can do is change the dataset and train the neural network. What to do? Write an adapter class that will translate the stream from mpeg2 -> OGG Theora and give into the neural network.

According to the classical scheme, client, target, adaptee and adapter are involved in the pattern. In this case, the client is a neural network that receives a video stream in Theora OGG, the target is the interface with which it interacts, adaptee is the interface that sends the video stream to mpeg2, the adapter converts mpeg2 to Theora OGG and sends via the target interface.

Sounds easy, right?

References

https://en.wikipedia.org/wiki/Adapter_pattern
https://refactoring.guru/design-patterns/adapter

Delegate Pattern

The delegate pattern refers to the basic design patterns.
Suppose we are developing a barbershop application. The application has a calendar for choosing the day for recording, tap on the date should open a list of barbers with the option of choice.
We implement naive binding of system components, combine the calendar and the screen using pointers to each other, to implement the list output:



// pseudocode

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

After a few days, the requirements change, before the list is displayed, you need to show offers with a choice of services (beard haircuts, etc.) but not always, on all days except Saturday.
We add the check Saturday today or not to the calendar, depending on it we call the method of the list of barbers or the list of services, for clarity I will demonstrate:



// pseudocode

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

After a week, we are asked to add a calendar to the feedback screen, and at this moment the first architectural oh happens!
What to do? After all, the calendar is tightly connected to the haircut recording screen.
wow! phew! Oh oh
If you continue to work with such a crazy application architecture, you should make a copy of the entire calendar class and associate this copy with the feedback screen.
Ok, it seems good, then we added a few more screens and several copies of the calendar, and then the X moment came. We were asked to change the design of the calendar, that is, now we need to find all copies of the calendar and add the same changes to all. This “approach” greatly affects the speed of development, increases the chance to make a mistake. As a result, such projects find themselves in a trough, when even the author of the original architecture does not understand how copies of his classes work, other hacks added along the way fall apart on the fly.
What had to be done, but it is better that it’s not too late to start doing? Use the delegation pattern!
Delegation is a way to pass class events through a common interface. The following is an example delegate for a calendar:


protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

Now add the work code with the delegate to the sample code:



// pseudocode

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

As a result, we untied the calendar from the screen at all, when selecting a date from the calendar, it sends a date selection event – * delegates * the processing of the event to the subscriber; the subscriber is the screen.
What benefits do we get in this approach? Now we can change the calendar and the logic of the screens independently of each other, without duplicating classes, simplifying further support; Thus, the “sole responsibility principle” of the implementation of the system components is implemented, the DRY principle is respected.
When using delegation, you can add, change the logic for displaying windows, the order of anything on the screen, and this will absolutely not affect the calendar and other classes, which objectively and should not participate in processes not directly related to them.
Alternatively, programmers who are not particularly bothered use sending messages via a common bus, without writing a separate protocol / interface for the delegate, where it would be better to use delegation. I wrote about the shortcomings of this approach in the last post – “Pattern Observer”.

References

https://refactoring.guru/replace-inheritance-with-delegation

Death-Mask Release!

Today I release Death-Mask game, based on Flame Steel Engine, Flame Steel Engine Game Toolkit and other cool technologies.

https://demensdeum.com/games/deathMask/

In this game you will play a role of Revil-Razorback, guy who wants to find mysterious artifact called the Death-Mask. This artifacts gives an eternal life to it’s owner. You will walk through endless techno-maze, and die many many times, before you find want you want.

There will be 3D models updates, minor gameplay changes in the distant future.

Death Mask – Cyber Fantasy Adventure in Endless Techno-Maze. Prepare to die!

Observer Pattern

The Observer pattern refers to behavioral patterns of design.
The pattern allows you to send an object state change to subscribers using a common interface.
Suppose we are developing a messenger for programmers, we have a chat screen in the application. When you receive a message with the text “problem” and “error” or “something is wrong”, you need to paint the error list screen and the settings screen in red.
Next, I will describe 2 options for solving the problem, the first is simple but extremely difficult to support, and the second is much more stable in support, but it requires skills! during initial implementation.

Common Bus

All implementations of the pattern contain sending messages when data changes, subscribing to messages, and further processing in methods. The shared bus option contains a single object (a singleton is usually used) that provides message dispatch to recipients.
Ease of implementation is as follows:

  1. The object sends an abstract message to the shared bus
  2. Another object subscribed to the shared bus catches the message and decides to process it or not.

One of the implementation options available from Apple (NSNotificationCenter subsystem), message header matching is added to the name of the method that is called by the recipient upon delivery.
The biggest drawback of this approach is that with a further change in the message, you will first need to remember and then manually edit all the places where it is processed, sent. There is a case of quick initial implementation, further long, complex support, requiring a knowledge base for correct operation.

Multicast Delegate

In this implementation, we will make the final class of the multicast delegate, as well as in the case with the common bus, objects can be subscribed to receive “messages” or “events”, however, the tasks of parsing and filtering messages are not assigned to the shoulders of objects. Instead, subscriber classes should implement the delegate’s multicast methods by which it notifies them.
This is realized by using the interfaces / protocols of the delegate, when changing the general interface, the application will stop compiling, at this point it will be necessary to change all the places of processing this message, without having to keep a separate knowledge base for remembering these places. Compiler is your friend.
In this approach, the team’s productivity is increased, since there is no need to write, store documentation, there is no need for a new developer to try to understand how a message is processed, its arguments, instead it works with a convenient and intuitive interface, so the documentation paradigm is implemented through code.
The multicast delegate itself is based on the delegate pattern, which I will write about in the next note.

References

https://refactoring.guru/design-patterns/observer

Proxy Pattern

Proxy pattern refers to structural design patterns.
The pattern describes the technique of working with the class through the class layer – proxy. The proxy allows you to change the functionality of the original class, with the ability to preserve the original behavior, while maintaining the original class interface.
Imagine the situation – in 2015, one of the countries of Western Europe decides to record all requests to the websites of users of the country, in order to improve statistics and an in-depth understanding of the political view of citizens.
Imagine a pseudo-code of naive gateway implementation used by citizens to access the Internet:


class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

In the above code, we create an Internet router class, with a pointer to an object that provides Internet access. When a client requests a site, we will return a response from the Internet.

Using the Proxy pattern and the singleton antipattern, add the logging functionality of the client name and URL:


class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

Due to saving the original InternetRouter interface in the InternetRouterProxy proxy class, it is enough to replace the initialization class from InternerRouter to its proxy, no more changes to the code base are required.

References

https://refactoring.guru/design-patterns/proxy

Death-Mask Wild Beta

Game Death-Mask goes into the status of a public beta (wild beta)
Reworked the main menu screen of the game, added a view of the blue zone of the techno-labyrinth, with pleasant music in the background.

Next, I plan to rework the gameplay controller, add smooth movement like in old shooters, high-quality 3D models of boxes, weapons, enemies, the ability to move to other levels of the techno-maze not only through portals (elevators, doors, fall through holes in the floor, holes in the walls), add a little variety to the environment of the generated maze. I will also work on the game balance.
Skeletal animation will be added at the polishing stage before release.