We are advancing through our journey of building various real-world applications. We have explored the Command, Observer, and Strategy patterns in the previous units. In this unit, we will integrate all three behavioral patterns into different scenarios to solve real-world problems.
Before diving into the coding exercise, let's have a quick recap of what each pattern does. The Command
pattern encapsulates a request as an object, allowing clients to parameterize and queue requests. The Observer
pattern defines a one-to-many dependency between objects, ensuring that when one object changes state, all its dependents are notified and updated automatically. The Strategy
pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Clients can choose the algorithm to use at runtime.
Let us consider one scenario of using the Command
and Observer
patterns together to build a chat application.
The Command
pattern encapsulates a request as an object, supporting the parameterization of clients with different requests and enabling undoable operations. Here’s a quick sample demonstrating how to encapsulate the action of showing a message in the chat room:
We define a base Command
class with an execute
method that needs to be implemented by subclasses. This serves as the blueprint for all command objects, ensuring they adhere to a common execution interface.
JavaScript1class Command { 2 execute() { 3 throw "Execute method must be implemented"; 4 } 5}
Next, we define a ChatRoom
class with a method to display and propagate messages. The sendMessage
function in the ChatRoom
class will be defined further down below.
JavaScript1class ChatRoom { 2 showMessage(message) { 3 console.log(`Message: ${message}`); 4 } 5}
The ChatCommand
class inherits from Command
and encapsulates the action of showing a message. When creating a ChatCommand
, you set the message and the chat room where the message will be displayed, then execute this command to display the message.
Now, we integrate the Observer
pattern to enable our chat application to notify users about incoming messages. Each User
instance represents an observer that can receive messages, while the ChatRoom
acts as the subject that notifies its users.
JavaScript1class ChatCommand extends Command { 2 constructor(chatRoom, message) { 3 super(); 4 this.chatRoom = chatRoom; 5 this.message = message; 6 } 7 8 execute() { 9 this.chatRoom.showMessage(this.message); 10 this.chatRoom.sendMessage(this.message); 11 } 12}
We define a User
class where each user can receive and print messages. This will allow users to get real-time updates from the chat room.
JavaScript1class User { 2 constructor(name) { 3 this.name = name; 4 } 5 6 receiveMessage(message) { 7 console.log(`${this.name} received message: ${message}`); 8 } 9}
Here, the ChatRoom
class is enhanced to hold a list of users and send messages to all users by iterating through the list, simulating the observer notification. Adding the Observer
pattern ensures that new messages are immediately communicated to all users.
JavaScript1class ChatRoom { 2 constructor() { 3 this.users = []; 4 } 5 6 addUser(user) { 7 this.users.push(user); 8 } 9 10 showMessage(message) { 11 console.log(`Message: ${message}`); 12 } 13 14 sendMessage(message) { 15 this.users.forEach(user => user.receiveMessage(message)); 16 } 17}
Now, we will combine both the Command
and Observer
patterns to create a functional chat application. You will use commands to handle user inputs and observers to manage message broadcasting. This integration increases the modularity and clarity of the application structure, making it easier to maintain and extend.
-
Defining the ChatRoom
JavaScript1let chatRoom = new ChatRoom();
-
Creating User Instances
JavaScript1let user1 = new User("Alice"); 2let user2 = new User("Bob");
-
Adding Users to the ChatRoom
JavaScript1chatRoom.addUser(user1); 2chatRoom.addUser(user2);
-
Creating and Executing a ChatCommand
JavaScript1let chatCommand = new ChatCommand(chatRoom, "Hello, everyone!"); 2chatCommand.execute();
In this way, the chat room acts as a central hub, commands encapsulate user actions such as sending messages, and users observe and receive messages from the chat room.
Below is the combined structure of our chat application, illustrating how the Command
and Observer
patterns work together:
JavaScript1class Command { 2 execute() { 3 throw "Execute method must be implemented"; 4 } 5} 6 7class User { 8 constructor(name) { 9 this.name = name; 10 } 11 12 receiveMessage(message) { 13 console.log(`${this.name} received message: ${message}`); 14 } 15} 16 17class ChatRoom { 18 constructor() { 19 this.users = []; 20 } 21 22 addUser(user) { 23 this.users.push(user); 24 } 25 26 showMessage(message) { 27 console.log(`Message: ${message}`); 28 } 29 30 sendMessage(message) { 31 this.users.forEach(user => user.receiveMessage(message)); 32 } 33} 34 35class ChatCommand extends Command { 36 constructor(chatRoom, message) { 37 super(); 38 this.chatRoom = chatRoom; 39 this.message = message; 40 } 41 42 execute() { 43 this.chatRoom.showMessage(this.message); 44 this.chatRoom.sendMessage(this.message); 45 } 46} 47 48const chatRoom = new ChatRoom(); 49const user1 = new User("Alice"); 50const user2 = new User("Bob"); 51 52chatRoom.addUser(user1); 53chatRoom.addUser(user2); 54 55const chatCommand = new ChatCommand(chatRoom, "Hello, everyone!"); 56chatCommand.execute(); 57// Outputs: 58// Message: Hello, everyone! 59// Alice received message: Hello, everyone! 60// Bob received message: Hello, everyone!
By the end of this unit, you will have a functional chat application in which messages can be sent and received efficiently. You will also have a deeper understanding of how combining multiple behavioral patterns can solve complex real-world problems effectively. Let's get started with the practice section and see these concepts come to life in our chat application!