- Backend Weekly
- Posts
- Part 10: Mediator and Iterator Design Patterns (GoF)
Part 10: Mediator and Iterator Design Patterns (GoF)
We will explore the Mediator and Iterator Design Patterns under the Behavioral Design Patterns.
Hello “👋
Welcome to another week, another opportunity to become a Great Backend Engineer.
Today’s issue is brought to you by Masteringbackend → A great resource for backend engineers. We offer next-level backend engineering training and exclusive resources.
Before we get started, I have a few announcements:
MBProjects is a project-based learning platform designed specifically for backend engineers. It’s your opportunity to work on 100+ real-world projects that mirror actual problems you’d encounter in the industry.
With MBProjects, you’ll get access to:
Pre-built Frontend: Skip the hassle of designing the Frontend and focus purely on building powerful backends.
Comprehensive Project Requirement Documents (PRDs): Know exactly what to build and why, with clear goals and specifications.
Starter Files & Complete Setup Guides: Jump right into coding with everything set up for you.
Task Breakdowns & Progress Tracking: Step-by-step instructions to guide you from start to finish.
Community Support: Connect with a group of builders like yourself to share progress, get feedback, and collaborate on projects.
Certificates of Completion: Showcase your skills to employers or on LinkedIn as you complete each project.
All of this is available for just $12/month—but only for a limited time! After launch, the price will go up to $24/month.
Interested? Reply “Interested” or 👉 Claim Your Special Launch Price Now 👈
When you log in, click on “Unlock Pro Now”
Learn how to make AI work for you.
The Rundown is the world’s largest AI newsletter, with over 700,000+ early adopters staying up-to-date with the latest AI news, and learning how to apply it in their work in just a 5 minute read per day.
Their expert team spends all day researching and talking with industry experts.
They send updates on the latest AI news and how to apply it in 5 minutes a day.
You learn how to become 2x more productive by leveraging AI.
Now, back to the business of today.
In this series, I will explore Design Patterns, their types, the GoF design patterns, drawbacks, and benefits for backend engineers.
This comes from my new Vue.js book on “Vue Design Patterns”. However, I’m only transferring the knowledge to backend engineers in this series.
Today, we will explore the Mediator and Iterator Design Patterns under the Behavioral Design Patterns.
Let’s get started quickly.
What is a Mediator Pattern?
The mediator pattern defines an object that encapsulates how objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly.
The Mediator Pattern is useful because it centralizes and manages interactions between multiple objects, promoting loose coupling and reducing dependencies among them. Instead of objects communicating directly with each other, they interact through a mediator, which coordinates their interactions.
This pattern is particularly beneficial when you have many interconnected objects whose interactions need to be simplified. By centralizing the control logic, the Mediator Pattern enhances modularity, maintainability, and flexibility.
Let’s take a look at an example, in an e-commerce system, multiple components like inventory, billing, and shipping might need to communicate during order processing. With a Mediator
, you can add or modify these components without altering existing code.
// Mediator Interface
class OrderMediator {
notify(sender, event) {
throw new Error("Method 'notify()' must be implemented.");
}
}
First, we created a mediator interface called OrderMediator
with a notify
method inside for notifying users when they make an order.
// Concrete Mediator: Order Processor
class OrderProcessor extends OrderMediator {
constructor() {
super();
this.inventory = null;
this.billing = null;
this.shipping = null;
}
registerInventory(inventory) {
this.inventory = inventory;
}
registerBilling(billing) {
this.billing = billing;
}
registerShipping(shipping) {
this.shipping = shipping;
}
notify(sender, event) {
if (event === "orderPlaced") {
this.inventory.checkStock();
this.billing.processPayment();
this.shipping.arrangeShipping();
}
}
}
Next, we created a concrete class of the mediator pattern that extends the OrderMediator
class and override the notify
method.
Inside this class, we registered different methods to perform the activities that we wanted such as registerInventory
, registerBilling
, and registerShipping
.
The notify method is called to send out different notifications once a user places an order.
// Colleague: Inventory
class Inventory {
setMediator(mediator) {
this.mediator = mediator;
}
checkStock() {
console.log("Checking stock...");
this.mediator.notify(this, "stockChecked");
}
}
// Colleague: Billing
class Billing {
setMediator(mediator) {
this.mediator = mediator;
}
processPayment() {
console.log("Processing payment...");
this.mediator.notify(this, "paymentProcessed");
}
}
// Colleague: Shipping
class Shipping {
setMediator(mediator) {
this.mediator = mediator;
}
arrangeShipping() {
console.log("Arranging shipping...");
this.mediator.notify(this, "shippingArranged");
}
}
The code snippet above creates all the activities we want to happen when a user places an order such as Inventory
, Billing
, and Shipping
.
// Client code
const orderProcessor = new OrderProcessor();
const inventory = new Inventory();
const billing = new Billing();
const shipping = new Shipping();
orderProcessor.registerInventory(inventory);
orderProcessor.registerBilling(billing);
orderProcessor.registerShipping(shipping);
inventory.setMediator(orderProcessor);
billing.setMediator(orderProcessor);
shipping.setMediator(orderProcessor);
orderProcessor.notify(null, "orderPlaced"); // Output: Checking stock... // Processing payment... // Arranging shipping...
Lastly, here’s how you will implement the mediator pattern in your client code. Here, we registered all the activities and set the mediator for each activity respectively.
Adding new components (e.g., notifications) or modifying existing ones (like shipping rules) only requires changes in the OrderProcessor
mediator. The pattern makes the system more flexible and extensible, allowing it to handle complex processes without extensive modifications.
Why the Mediator Pattern is Useful
The Mediator Pattern is particularly beneficial in systems with multiple interacting objects. Its main advantages include:
Reduces coupling: Components interact only with the mediator, reducing dependencies and simplifying relationships.
Simplifies many-to-many relationships: Centralizes interactions, making the system easier to maintain.
Promotes the Single Responsibility Principle: Each component focuses on its primary role, while the mediator manages interactions.
Supports extensibility: New components or interactions can be added by modifying the mediator, without changing existing components.
Facilitates reuse of components: Components are independent of each other and can be reused in other contexts.
By using the Mediator Pattern, you can build systems that are more modular, maintainable, and easier to extend, especially when dealing with complex component interactions.
What is an Iterator Pattern?
The iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
The Iterator Pattern is useful because it provides a standardized way to access elements in a collection sequentially without exposing the underlying structure of the collection.
This pattern decouples the traversal logic from the collection, making it easier to add new types of collections or modify existing ones without changing the code that interacts with them.
It also supports various ways of traversing collections (e.g., forward, backward, filtering). The Iterator Pattern is particularly beneficial when working with complex or varied data structures where different traversal strategies might be needed.
In a music application, there might be different types of playlists, such as albums, artist-based playlists, or custom playlists. The Iterator Pattern allows users to navigate these playlists consistently, even if they’re implemented differently.
// Iterator Interface
class Iterator {
next() {
throw new Error("Method 'next()' must be implemented.");
}
hasNext() {
throw new Error("Method 'hasNext()' must be implemented.");
}
}
The code snippet above creates an Iterator
class with a next
and hasNext
method to be implemented.
// Concrete Iterator
class PlaylistIterator extends Iterator {
constructor(playlist) {
super();
this.playlist = playlist;
this.index = 0;
}
next() {
return this.playlist[this.index++];
}
hasNext() {
return this.index < this.playlist.length;
}
}
Next, we created a PlaylistIterator
class that extends and overrides the methods of the Iterator
class.
// Collection Interface
class Playlist {
createIterator() {
throw new Error("Method 'createIterator()' must be implemented.");
}
}
// Concrete Collection
class SongPlaylist extends Playlist {
constructor(songs) {
super();
this.songs = songs;
}
createIterator() {
return new PlaylistIterator(this.songs);
}
}
Next, we created a Playlist
and SongPlaylist
classes which shows how songs are added to the playlist and an iterator that plays each song sequentially.
// Client code
const playlist = new SongPlaylist(["Song A", "Song B", "Song C"]);
const iterator = playlist.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
// Output:
// Song A
// Song B
// Song C
Lastly, this is how the iterator pattern is implemented in our client’s code. First, we added songs to the playlist using the SongPlaylist
class and use the createIterator
method to iterate through each of the songs.
The PlaylistIterator
provides a standardized way to traverse the SongPlaylist
, hiding the collection’s internal details. Client code can traverse any playlist in the same way, regardless of the specific collection structure, promoting consistency.
Why the Iterator Pattern is Useful
The Iterator Pattern is particularly beneficial in scenarios where you need a flexible and standardized way to traverse different types of collections. Its main advantages include:
Standardizes access to collections: Provides a consistent way to traverse collections, regardless of internal structure.
Promotes the Single Responsibility Principle: Separates traversal from data storage, enhancing modularity.
Supports different traversal strategies: Allows multiple ways of traversing collections without modifying them.
Enhances flexibility and extensibility: New traversal methods can be added easily by creating new iterators.
Supports multiple simultaneous iterations: Enables concurrent, independent iterations over the same collection.
By using the Iterator Pattern, you can build systems that are more modular, flexible, and maintainable, especially when working with complex data structures that require various traversal strategies.
That will be all for today. I like to keep this newsletter short.
Don’t miss it. Share with a friend
Did you learn any new things from this newsletter this week? Please reply to this email and let me know. Feedback like this encourages me to keep going.
See you on Next Week.
Remember to start learning backend engineering from our courses:
Top 5 Remote Backend Jobs this week
Here are the top 5 Backend Jobs you can apply to now.
👨💻 Prisma
✍️ Remote Senior Backend Software Engineer (Rust)
📍Remote
💰 Click on Apply for salary details
Click here to Apply for this role.
👨💻 Varo
✍️ Sr. Software Engineer, Data (Backend)
📍Remote, Worldwide
💰 Click on Apply for salary details
Click here to Apply for this role.
👨💻 Coursera
✍️ Staff Software Engineer - Backend
📍Remote, Worldwide
💰 Click on Apply for salary details
Click here to Apply for this role.
👨💻 Zoox
✍️ Backend Engineer (f/m/d)
📍Remote
💰 Click on Apply for salary details
Click here to Apply for this role.
Want more Remote Backend Jobs? Visit GetBackendJobs.com
Backend Engineering Resources
Whenever you're ready
There are 4 ways I can help you become a great backend engineer:
1. The MB Platform: Join 1000+ backend engineers learning backend engineering on the MB platform. Build real-world backend projects, track your learnings and set schedules, learn from expert-vetted courses and roadmaps, and solve backend engineering tasks, exercises, and challenges.
2. The MB Academy: The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.
3. MB Video-Based Courses: Join 1000+ backend engineers who learn from our meticulously crafted courses designed to empower you with the knowledge and skills you need to excel in backend development.
4. GetBackendJobs: Access 1000+ tailored backend engineering jobs, manage and track all your job applications, create a job streak, and never miss applying. Lastly, you can hire backend engineers anywhere in the world.
LAST WORD 👋
How am I doing?
I love hearing from readers, and I'm always looking for feedback. How am I doing with The Backend Weekly? Is there anything you'd like to see more or less of? Which aspects of the newsletter do you enjoy the most?
Hit reply and say hello - I'd love to hear from you!
Stay awesome,
Solomon
I moved my newsletter from Substack to Beehiiv, and it's been an amazing journey. Start yours here.
Reply