Pattern Composite

The Composite pattern is a structural design pattern; in domestic sources it is known as the “Composer”.
Let’s say we are developing an application – a photo album. The user can create folders, add photos there, and perform other manipulations. It is necessary to be able to show the number of files in folders, the total number of all files and folders.
It is obvious that you need to use a tree, but how to implement the tree architecture, with a simple and convenient interface? The Composite pattern comes to the rescue.

Sheila in Moonducks

Next, in Directory we implement the dataCount() method – by going through all the elements in the component array, adding up all their dataCount’s.
Everything is ready!
Below is an example in Go:
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())

}

Sources

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

Pattern Adapter

Benjamín Núñez González

The Adapter pattern is a structural design pattern.

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

Let’s say we are developing a system for determining the buyer’s goal in a store based on neural networks. The system receives a video stream from the store’s camera, identifies buyers by their behavior, and classifies them into groups. Types of groups: came to buy (potential buyer), just to look (gaper), came to steal something (thief), came to return goods (dissatisfied buyer), came drunk/high (potential troublemaker).

Like all experienced developers, we find a ready-made neural network that can classify monkey species in a cage based on a video stream, which was kindly made freely available by the Zoological Institute of the Berlin Zoo, we retrain it on a video stream from the store and get a working state-of-the-art system.

There is only one 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 it to the neural network.

According to the classic scheme, the pattern involves client, target, adaptee and adapter. The client in this case is a neural network receiving a video stream in OGG Theora, target is the interface with which it interacts, adaptee is the interface that outputs the video stream in mpeg2, the adapter converts mpeg2 to OGG Theora and outputs it via the target interface.

It seems simple, doesn’t it?

Sources

https://ru.wikipedia.org/wiki/Адтер_(проектированный)
https://refactoring.guru/ru/design-patterns/adapter

Delegate Pattern

The delegate pattern is one of the basic design patterns.
Let’s say we are developing a barbershop app. The app has a calendar for choosing a day for an appointment, and when you tap on a date, a list of barbers should open with the option to choose from.
We will implement a naive binding of system components, we will combine the calendar and the screen using pointers to each other, to implement the list output:


// псевдокод

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 displaying the list you need to show offers with a choice of services (beard trimming, etc.) but not always, on all days except Saturday.
We add a check to the calendar whether it is Saturday today or not, depending on it we call the method of the list of barbers or the list of services, for clarity I will demonstrate:


// псевдокод

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)
        }
    }
}

A week later we are asked to add a calendar to the feedback screen, and at this point the first architectural ouch happens!
What should I do? The calendar is tightly linked to the haircut appointment screen.
oh! oof! oh-oh
If you continue to work with such a crazy application architecture, you should make a copy of the entire calendar class and link this copy to the feedback screen.
Ok, so it seems good, then we added a few more screens and a few copies of the calendar, and then the X-moment came. We were asked to change the calendar design, that is, now we need to find all the copies of the calendar and add the same changes to all of them. This “approach” greatly affects the speed of development, increases the chance of making a mistake. As a result, such projects end up in a broken trough state, when even the author of the original architecture does not understand how the copies of his classes work, other hacks added along the way fall apart on the fly.
What should have been done, or better yet, what is not too late to start doing? Use the delegation pattern!
Delegation is a way to pass class events through a common interface. Here is an example of a delegate for a calendar:

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

Now let’s add code to work with the delegate to the example code:


// псевдокод

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 completely untied the calendar from the screen; when selecting a date from the calendar, it passes the date selection event – *delegates* the event processing to the subscriber; the subscriber is the screen.
What advantages do we get in this approach? Now we can change the calendar and screen logic independently of each other, without duplicating classes, simplifying further support; thus, the “single responsibility principle” of implementing system components is implemented, the DRY principle is observed.
When using delegation, you can add, change the logic of displaying windows, the order of anything on the screen, and this will not affect the calendar and other classes that objectively should not participate in processes not directly related to them.
Alternatively, programmers who don’t bother themselves much use sending messages via a common bus, without writing a separate protocol/delegate interface, where it would be better to use delegation. I wrote about the disadvantages of this approach in the previous note – “Observer Pattern”.

Sources

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

Observer Pattern

The Observer pattern is a behavioral design pattern.
The pattern allows sending a change in the state of an object to subscribers using a common interface.
Let’s say we are developing a messenger for programmers, we have a chat screen in our application. When receiving a message with the text “problem” and “error” or “something is wrong”, we need to paint the error list screen and the settings screen 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 requires turning on the brain during the initial implementation.

Common Bus

All implementations of the pattern contain sending messages when data changes, subscribing to messages, and further processing in methods. The variant with a common bus contains a single object (usually a singleton is used) that provides dispatching of messages to recipients.
The simplicity of implementation is as follows:

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

One of the implementation options available from Apple (NSNotificationCenter subsystem) adds matching of the message header to the name of the method that is called on the recipient upon delivery.
The biggest disadvantage of this approach is that when changing the message further, you will first need to remember and then manually edit all the places where it is processed and sent. This is a case of quick initial implementation, followed by long, complex support that requires a knowledge base for correct operation.

Multicast delegate

In this implementation, we will make a final class of multicast delegate, to which, as in the case of a common bus, objects can subscribe to receive “messages” or “events”, but the objects are not assigned the work of parsing and filtering messages. Instead, subscriber classes must implement the methods of the multicast delegate, with the help of which it notifies them.
This is implemented by using delegate interfaces/protocols; when the general interface changes, the application will stop assembling, at which point it will be necessary to redo all the places where this message is processed, without the need to maintain a separate knowledge base to remember these places. The compiler is your friend.
This approach increases team productivity, since there is no need to write or store documentation, there is no need for a new developer to try to understand how a message is processed, its arguments, instead, work occurs with a convenient and understandable interface, thus implementing the paradigm of documentation through code.
The multicast delegate itself is based on the delegate pattern, which I will write about in the next post.

Sources

https://refactoring.gu/ru/design-patterns/observer

Proxy Pattern

The Proxy pattern is a structural design pattern.
The pattern describes the technique of working with a class through a class interlayer – a 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 interface of the class.
Let’s imagine a situation – in 2015, one of the countries of Western Europe decides to record all requests to the websites of the country’s users, in order to improve statistics and gain a deeper understanding of the political moods of citizens.
Let’s present a pseudo-code of a naive implementation of a gateway that citizens use 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 code above, we create an Internet router class, with a pointer to an object providing access to the Internet. When a client requests a site, we return a response from the Internet.

Using the Proxy pattern and the singleton anti-pattern, we will add the functionality of logging 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 the preservation of the original InternetRouter interface in the InternetRouterProxy proxy class, it is enough to replace the initialization class from InternetRouter to its proxy, no further changes to the code base are required.

Sources

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

Pattern Prototype

The prototype pattern belongs to the group of generative design patterns.
Let’s say we are developing a dating app Tender, according to the business model we have a paid option to make copies of your own profile, changing the name automatically, and the order of the photos in some places. This is done so that the user can have the opportunity to manage several profiles with different sets of friends in the app.
By clicking on the button to create a copy of the profile, we need to implement copying of the profile, automatic name generation and re-sorting of photos.
Naive pseudocode implementation:

fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Now let’s imagine that other team members copy-pasted the copy code or came up with it from scratch, and after that a new field was added – likes. This field stores the number of profile likes, now you need to update *all* places where copying occurs manually, adding the new field. This is very long and difficult to maintain the code, as well as to test.
To solve this problem, the Prototype design pattern was invented. Let’s create a common Copying protocol, with a copy() method that returns a copy of the object with the necessary fields. After changing the entity fields, you will only need to update one copy() method, instead of manually searching and updating all the places containing the copy code.

Sources

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

State Machine and Pattern State

In this note I will describe the use of a state machine (State Machine), show a simple implementation, implementation using the State pattern. It is worth mentioning that it is undesirable to use the State pattern if there are less than three states, since this usually leads to unnecessary complication of code readability, associated problems in support – everything should be in moderation.

MEAACT PHOTO / STUART PRICE.

Lord of the Flags

Let’s say we are developing a video player screen for a media system of a civil aircraft. The player should be able to load a video stream, play it, allow the user to stop the loading process, rewind, and perform other operations common to a player.
Let’s say the player cached the next chunk of the video stream, checked that there are enough chunks for playback, started playing the fragment to the user and simultaneously continues downloading the next one.
At this point, the user rewinds to the middle of the video, i.e. now you need to stop playing the current fragment and start loading from a new position. However, there are situations in which this cannot be done – the user cannot control the playback of the video stream while he is shown a video about air safety. Let’s create a flag isSafetyVideoPlaying to check this situation.
The system should also be able to pause the current video and broadcast an announcement from the ship’s captain and crew via the player. Let’s create another flag, isAnnouncementPlaying. Plus, there is a requirement not to pause playback while displaying help on how to use the player, another flag, isHelpPresenting.

Pseudocode of a media player example:

class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

The above example is hard to read, and hard to maintain due to high variability (entropy). This example is based on my experience working with the codebase of *many* projects that did not use a state machine.
Each checkbox should “manage” the interface elements and business logic of the application in a special way; the developer, adding another checkbox, should be able to juggle them, checking and rechecking everything several times with all possible options.
Substituting into the formula “2 ^ number of flags” we can get 2 ^ 6 = 64 variants of application behavior for only 6 flags, all these combinations of flags will need to be checked and supported manually.
From the developer’s side, adding new functionality with such a system looks like this:
– We need to add the ability to show the airline’s browser page, and it should collapse like with movies if the crew members announce something.
– Ok, I’ll do it. (Oh, shit, I’ll have to add another flag and double-check all the places where the flags intersect, that’s a lot to change!)

Also the weak point of the flag system is making changes to the application behavior. It is very difficult to imagine how to quickly/flexibly change behavior based on flags, if after changing just one flag you have to recheck everything. This approach to development leads to a lot of problems, loss of time and money.

Enter The Machine

If you look closely at the flags, you can see that we are actually trying to process specific processes that occur in the real world. Let’s list them: normal mode, showing a safety video, broadcasting a message from the captain or crew members. Each process has a set of rules that change the behavior of the application.
According to the rules of the state machine pattern, we list all processes as states in enum, add such a concept as state to the player code, implement behavior based on the state, removing combinations on the flags. In this way, we reduce the options for testing exactly to the number of states.

Pseudocode:

enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

The big difference between a flag system and a state machine is the logical state switching funnel in the set(state: ..) method, which allows us to translate the human understanding of state into program code, without having to play logic games of converting flags into states in the future code maintenance.

State Pattern

Next I will show the difference between a naive implementation of a state machine and the state pattern. Let’s imagine that it was necessary to add 10 states, as a result the state machine class will grow to the size of a godobject, it will be difficult and expensive to maintain. Of course, this implementation is better than the flag system (with a flag system the developer will shoot himself first, and if not, then seeing 2 ^ 10 = 1024 variations the QA will hang himself, however if both of them *don’t notice* the complexity of the task, then the user will notice it, for whom the application will simply refuse to work with a certain combination of flags)
When there are a large number of states, it is necessary to use the State pattern.
Let’s put the set of rules into the State protocol:

protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

Let’s take the implementation of the set of rules into separate states, for example, the code of one state:

class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

Next, we will create a context with which each state will work, and integrate the state machine:

final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	остальные возможные события
}

Application components work with the context through public methods, state objects themselves decide from which state to which to make the transition using the state machine inside the context.
In this way, we have implemented the decomposition of the God Object, maintaining the changing state will be much easier, thanks to the compiler tracking changes in the protocol, reducing the complexity of understanding states due to the reduction in the number of lines of code and focusing on solving a specific problem of the state. Also, now it is possible to divide the work in the team, giving the implementation of a specific state to team members, without worrying about the need to “resolve” conflicts, which happens when working with one large class of state machine.

Sources

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

Template method

The template method is a behavioral design pattern. The pattern describes a way to replace part of a class’s logic on demand, while leaving the common part unchanged for descendants.

Cuban Cars

Let’s say we are developing a bank client, let’s consider the task of developing an authorization module – the user should be able to log in to the application using abstract login data.
The authorization module must be cross-platform, support different authorization technologies and storage of encrypted data of different platforms. To implement the module, we choose the cross-platform language Kotlin, using the abstract class (protocol) of the authorization module, we will write an implementation for the MyPhone phone:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Now for each phone/platform we will have to duplicate the code for sending authorization to the server, there is a violation of the DRY principle. The example above is very simple, in more complex classes there will be even more duplication. To eliminate code duplication, you should use the Template Method pattern.
We will move the common parts of the module into immutable methods, and transfer the functionality of transmitting encrypted data to specific platform classes:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Sources

https://refactoring.guru/ru/design- patterns/template-method

Source code

https://gitlab.com/demensdeum/patterns/< /p>