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

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

Prototype Pattern

The pattern “Prototype” refers to the group of creational patterns.
Suppose we are developing applications for dating Tender, according to the business model, we have a paid opportunity to make copies of our own profile by changing the name automatically, and the order of photos in places. This is done in order for the user to be able to conduct several profiles at once with a different set of friends in the application.
By clicking on the button to create a copy of the profile, we need to implement a profile copy, auto name generation and re-sort the photos.
Pseudocode:


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

Now imagine that other team members made a copy-paste copy code or invented 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 * of the place where copying takes place manually, adding a new field. It is very long and difficult to maintain the code, as in testing.
To solve this problem, a prototype design pattern was invented. Let’s create a common copying protocol, with the copy () method which returns a copy of the object with the required fields. After changing the entity fields, you will need to update only one copy() method, instead of manually searching and updating all places containing the copy code.

References

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

State machine and State pattern

In this article I will describe the use of the state machine, I will show a simple implementation, an implementation using the State pattern. It is worth mentioning that the use of the State pattern is undesirable in the presence of fewer than three states, since this usually leads to an excessive complication of the readability of the code, accompanying support problems – everything should be in moderation.

MEAACT PHOTO / STUART PRICE.

Flags Ruler

Suppose we are developing a video player screen for a civil aircraft media system, the player must be able to download the video stream, play it, allow the user to stop the download process, rewind and perform other ordinary operations for the player.
Suppose the player has cached the next chunk of the video stream, checked that there were enough chunks to play, began to play the fragment to the user and at the same time continues to download the next one.
At this point, the user rewinds to the middle of the video, so now you need to stop playing the current track, and start loading from a new position. However, there are situations in which this can not be done – the user can not control the playback of the video stream, while he is shown a video about safety in the air. Get the isSafetyVideoPlaying flag to check this situation.
Also, the system should be able to pause the current video and broadcast an alert from the captain of the vessel and crew through the player. Let’s get another flag isAnnouncementPlaying. Plus, there was a requirement not to pause the playback while displaying help on working with the player, one more flag isHelpPresenting.

Pseudocode:


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 poorly readable, such code is difficult to maintain due to the large variability (entropy) This example is based on my experience with the code base * of many * projects where the state machine was not used.
Each flag should “manage” the interface elements in a special way; the application’s business logic, the developer, adding the next flag, should be able to juggle them, checking and re-checking everything several times with all possible options.
Substituting into the formula “2 ^ number of flags” you can get 2 ^ 6 = 64 variants of application behavior for a total of 6 flags, all these combinations of flags will need to be checked and maintained manually.
From the developer’s side, adding a new functionality with this system looks like this:
– You need to add the ability to show the browser page of the airline, while it should be minimized as with movies, if the crew members announce something.
– Ok, I will. (Oh, damn, you’ll have to add one more flag, and double-check all the places where the flags intersect, that’s how much you need to change!)

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

Enter The Machine

If you take a good look at the flags, you can understand that in fact we are trying to handle specific processes taking place in the real world. We list them: the usual mode, the display of video security, broadcast messages from the captain or crew. For each process, a set of rules is known that change the behavior of the application.
According to the rules of the state machine, we will list all the processes as states in enum, add such a concept as state to the player code, implement the behavior based on the state, removing the combinations on the checkboxes. Thus, we reduce the options for testing to exactly 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 huge difference between the flag system and the state machine lies in the logic state switching funnel in the set (state: ..) method, it allows you to translate the human understanding of the state into the program code, without having to play logic games to convert the flags to states with further support of the code .

State pattern

Next, I will show the difference between the naive implementation of the state machine and the state pattern. Imagine that you need to add 10 steits, 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, (if the flag system is shot first by the developer, and if not, after seeing 2 ^ 10 = 1024 variations, QA hangs, but if both of them * do not notice the complexity of the task, then the user who has the application just refuses to work with a certain combination of flags)
With a large number of states, it is necessary to use the State pattern.
We will issue a set of rules in the protocol of the State:


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

We will carry out the implementation of the set of rules in 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, create a context with which each state will work, 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)
	}

	…
	Other events
}

Application components work with the context through public methods, the state objects themselves decide from which state to which to make the transition using the state machine inside the context.
Thus, we have implemented the God Object decomposition, and it will be much easier to maintain a changing state by tracking the changes in the protocol by the compiler, reducing the complexity of understanding the states due to reducing the number of lines of code and focusing on solving a specific state task. Also, you can now share teamwork, giving the implementation of a particular state to team members, without worrying about the need to “resolve” conflicts, which happens when working with one large class of state machines.

References

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

Skeletal Animation (part 1 – shader)

In this article, I will describe my understanding of skeletal animation, which is used in all modern 3D engines to animate characters, game environments, etc.
I will start the description from the most simple part – the vertex shader, because the whole way of the calculations, no matter how complicated it was, ends with the transfer of the prepared data to render in the vertex shader.

Skeletal animation after calculating the CPU enters the vertex shader.
I recall the vertex formula without skeletal animation:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vertex;
For those who do not understand how this formula originated, you can read my article describing the principle of working with matrices to display 3D content in the context of OpenGL.
For the rest – the formula for the implementation of skeletal animation:
” vec4 animatedVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectionMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Ie the final matrix of bone transformation is multiplied by the vertex and the weight of this matrix relative to the vertex. Each vertex can be animated with 4 bones, the force of the impact is regulated by the bone weight parameter, the sum of the effects should be equal to one.
What to do if the vertex is affected by less than 4 bones? It is necessary to divide the weight between them, the effect of the rest to be equal to zero.
Mathematically multiplying a weight by a matrix is ​​called “Multiplying a matrix by a scalar”. Multiplication by a scalar allows you to sum up the effects of the matrices on the final vertex.

The bone transformation matrixes themselves are passed in an array. Moreover, the array contains the matrix for the entire model as a whole, and not for each mesh separately.

But for each vertex the following information is transmitted separately:
– Matrix index that affects vertex
– The weight of the matrix that affects the vertex
More than one bone is transmitted, usually 4 bones per vertex are used.
Also the sum of the weights of the 4 bones should always be equal to one.
Next, consider how it looks in the shader.
Array of matrices:
“uniform mat4 bonesMatrices[kMaxBones];”

Information on the effect of 4 bones on each vertex:
“attribute vec2 bone0info;”
“attribute vec2 bone1info;”
“attribute vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – in the X coordinate, store the bone index (and translate to int in the shader), in the Y coordinate, the weight of the impact of the bone on the vertex. Why do you have to transfer this data in a two-dimensional vector? Because GLSL does not support the transfer of readable C structures with correct fields to the shader.

Below is an example of obtaining the necessary information from a vector, for further substitution in the formula animatedVertex:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Now the vertex structure that fills up on the CPU should look like this:
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

The structure of the vertex buffer is filled once at the time of loading the model, but the transformation matrices are transferred from the CPU to the shader at each rendering frame.

In the remaining parts, I will describe the principle of calculating animation on the CPU, before passing to the vertex shader, I will describe the tree of bones, the passage through the hierarchy of animation-model-node-mesh, matrix interpolation.

References

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

Source Code

https://gitlab.com/demensdeum/skeletal-animation

Template Method Pattern

The template method refers to behavioral design patterns. The pattern describes how to replace parts of class logic on demand, leaving the common part unchanged for posterity.

Cuban Cars

Suppose we are developing a bank client, 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 technologies of authorization and storage of encrypted data of different platforms. To implement the module, we choose the Kotlin cross-platform language using the abstract class (protocol) of the authorization module, we will write the 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 to send authorization to the server, there is a violation of the principle of DRY. The above example is very simple, there will be even more duplication in more complex classes. To eliminate code duplication, use the Template method pattern.
We will carry out the common parts of the module in immutable methods, we transfer the functionality of the transfer of encrypted data to specific classes of platforms:


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

References

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

Source code

https://gitlab.com/demensdeum/patterns/

Bridge Pattern

The Bridge pattern refers to the structural design patterns. It allows you to abstract the implementation of class logic using the transfer of logic into a separate abstract class. Sounds easy, right?

Suppose we implement a spam bot that should be able to send messages to different types of instant messengers.
We implement using a common protocol:


protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Now let’s imagine the situation of the release of a new, faster message sending protocol for the iSekU messenger. To add a new protocol, it will be necessary to duplicate the implementation of the iSekU bot, changing only a small part of it. It is not clear why to do this if only a small part of the class logic has changed. With this approach, the principle of DRY is violated; with the further development of the product, the lack of flexibility will make itself known by mistakes and delays in the implementation of new opportunities.
We’ll take out the logic of the protocol in an abstract class, thus implementing the Bridge pattern:


protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

One of the advantages of this approach is undoubtedly the ability to extend the functionality of the application, by writing plug-ins / libraries that implement the abstracted logic, without changing the code of the main application.
What is the difference with the Strategy pattern? Both patterns are very similar, but the Strategy describes the switching * of algorithms *, while the Bridge allows switching large parts * of any complex logic *.

References

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

Source Code

https://gitlab.com/demensdeum/patterns/