Mediator Design Pattern in Swift


Greetings, traveler!

We continue to discuss Design Patterns. It is the turn of another Behavioral Design Pattern, the Mediator. The Mediator is a design pattern that helps reduce chaotic dependencies between objects by restricting direct connections and forcing objects to interact through an intermediary. This pattern helps create a more organized and predictable system and provides a way of object decoupling.

Example of usage

So, straight to the point? Let’s look at the example of updating the operating system version of your iPhone. You have an iPhone, right?

The software development team needs to release an iOS update to do this. The marketing team should then describe the new features clearly and engagingly and notify users about the update. We, as users, will promptly download and install the update.

Now, let’s put this whole story into code. 

So, we have a development team and three other members who interact with it when an operating system update is released. Let’s start with the operating system.

struct iOS {
    var version: Double
}

Now, we will create protocols for the user and the marketing department — the mediator.

protocol UserProtocol {
    func handleNotification(about: iOS)
}

protocol MediatorProtocol {
    func notify(about os: iOS)
}

And now, let’s take a look at the implementation of these protocols.

final class User: UserProtocol {
    func handleNotification(about os: iOS) {
        print("Update iOS version to \(os.version)")
    }
}

final class MarketingDepartment: MediatorProtocol {
    
    private let users: [UserProtocol]
    
    init(users: [UserProtocol]) {
        self.users = users
    }
    
    func notify(about os: iOS) {
        users.forEach {
            $0.handleNotification(about: os)
        }
    }
    
}

So, who will make the update itself? Of course, this is our favorite Apple Software Department.

final class SoftwareDepartment {
    
    private let mediator: MediatorProtocol
    
    init(mediator: MediatorProtocol) {
        self.mediator = mediator
    }
    
    func update(os: iOS) {
        mediator.notify(about: os)
    }
}

Now we can use it all together.

let users = [User(), User(), User()]
let marketing = MarketingDepartment(users: users)
let development = SoftwareDepartment(mediator: marketing)
let os = iOS(version: 18.0)
        
development.update(os: os)

Conclusion

This behavioral design pattern allowed us to avoid direct interaction between the software department and users. The marketing department has taken on this responsibility. As a result, we have avoided the difficulties associated with dependency injection, and the objects are not interconnected. That’s convenient! Now, let’s move on to the next design pattern, the Memento pattern.