The Chain Of Responsibility Pattern

What is a Chain Of Responsibility Pattern? The Chain Of Responsibility Pattern helps create a chain of objects to examine requests. Each object in turn examines a request and either handles it or passes onto the next object in the chain. Source What problems does it solve? The Chain Of Responsibility Pattern (CoR) helps solve following problems: Dynamic Request Handling: It enables dynamic assignment of responsibilities at runtime. Handlers can be added, removed, or reordered without affecting the client’s code. This flexibility allows for easier maintenance and extension of the system. Decoupling Sender and Receiver: In traditional systems, a sender often needs to know the exact receiver of a request, leading to tight coupling between them. The CoR pattern decouples senders from receivers by allowing multiple objects to handle a request without the sender knowing the specific handler. Real-world code example // Protocol defining the handler interface protocol PurchaseHandler { var next: PurchaseHandler? { get set } func handleRequest(amount: Double) } // Concrete handlers class SmallPurchaseHandler: PurchaseHandler { var next: PurchaseHandler? let maxAmount: Double = 100.0 func handleRequest(amount: Double) { if amount <= maxAmount { print("SmallPurchaseHandler: Purchase approved for $\(amount)") } else if let nextHandler = next { print("SmallPurchaseHandler: Passing request to next handler") nextHandler.handleRequest(amount: amount) } else { print("SmallPurchaseHandler: No handler available, purchase rejected") } } } class MediumPurchaseHandler: PurchaseHandler { var next: PurchaseHandler? let maxAmount: Double = 500.0 func handleRequest(amount: Double) { if amount <= maxAmount { print("MediumPurchaseHandler: Purchase approved for $\(amount)") } else if let nextHandler = next { print("MediumPurchaseHandler: Passing request to next handler") nextHandler.handleRequest(amount: amount) } else { print("MediumPurchaseHandler: No handler available, purchase rejected") } } } class LargePurchaseHandler: PurchaseHandler { var next: PurchaseHandler? func handleRequest(amount: Double) { print("LargePurchaseHandler: Purchase approved for $\(amount)") } } // Usage func main() { let smallHandler = SmallPurchaseHandler() let mediumHandler = MediumPurchaseHandler() let largeHandler = LargePurchaseHandler() // Connecting handlers into a chain smallHandler.next = mediumHandler mediumHandler.next = largeHandler smallHandler.handleRequest(amount: 50.0) smallHandler.handleRequest(amount: 200.0) smallHandler.handleRequest(amount: 1000.0) } Thank you for reading! 😊

March 15, 2024 · 2 min · Dmytro Chumakov

The State Pattern

What is a State Pattern? The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. Source What problems does it solve? Complex conditional logic: When an object’s behavior depends on its internal state, it often leads to complex conditional statements. The State pattern simplifies this by encapsulating each state and its behavior in separate classes, making the code more readable and maintainable. State-specific behavior: Objects often need to change their behavior based on their state. The State pattern allows objects to delegate behavior to state objects, which can vary independently. This promotes better encapsulation and separation of concerns. Adding new states: When new states need to be added, the State pattern makes it easier to extend the functionality without modifying existing code. New states can be added by creating new state classes and integrating them into the existing context, without changing the context class itself. Real-world code example // Define the VendingMachine protocol protocol VendingMachineState { func insertCoin() func dispenseItem() } // Define concrete states class NoCoinState: VendingMachineState { private let vendingMachine: VendingMachine init(vendingMachine: VendingMachine) { self.vendingMachine = vendingMachine } func insertCoin() { print("Coin inserted") // Transition to the HasCoinState vendingMachine.changeState(newState: vendingMachine.hasCoinState) } func dispenseItem() { print("Please insert a coin first") } } class HasCoinState: VendingMachineState { private let vendingMachine: VendingMachine init(vendingMachine: VendingMachine) { self.vendingMachine = vendingMachine } func insertCoin() { print("Coin already inserted") } func dispenseItem() { if vendingMachine.inventoryCount > 0 { print("Item dispensed") vendingMachine.decreaseInventory() // Transition to the NoCoinState vendingMachine.changeState(newState: vendingMachine.noCoinState) } else { print("Out of stock") } } } // Define the VendingMachine class class VendingMachine { var inventoryCount: Int = 5 var currentState: VendingMachineState! var noCoinState: VendingMachineState! var hasCoinState: VendingMachineState! init() { noCoinState = NoCoinState(vendingMachine: self) hasCoinState = HasCoinState(vendingMachine: self) currentState = noCoinState } func changeState(newState: VendingMachineState) { currentState = newState } func insertCoin() { currentState.insertCoin() } func dispenseItem() { currentState.dispenseItem() } func decreaseInventory() { inventoryCount -= 1 } } // Usage let vendingMachine = VendingMachine() vendingMachine.dispenseItem() vendingMachine.insertCoin() vendingMachine.insertCoin() vendingMachine.dispenseItem() vendingMachine.dispenseItem() Thank you for reading! ...

March 10, 2024 · 2 min · Dmytro Chumakov

The Dependency Inversion Principle

What is a Dependency Inversion Principle? The Dependency Inversion Principle means that high-level modules should not depend on low-level modules. Source Source What problems does it solve? The Dependency Inversion Principle (DIP) helps solve: Rigidity Fragility Immobility problems Real-world code example Violation of DIP // High-level module directly depending on low-level modules class MessageService { func sendMessageViaEmail(message: String) { let emailSender = EmailSender() emailSender.sendMessage(message: message) } func sendMessageViaSMS(message: String) { let smsSender = SMSSender() smsSender.sendMessage(message: message) } func sendMessageViaPushNotification(message: String) { let pushNotificationSender = PushNotificationSender() pushNotificationSender.sendMessage(message: message) } } Adhering to DIP // Protocol defining the interface for sending messages protocol MessageSender { func sendMessage(message: String) } // High-level module depending on abstraction (MessageSender protocol) class MessageService { private let messageSender: MessageSender init(messageSender: MessageSender) { self.messageSender = messageSender } func sendMessage(message: String) { messageSender.sendMessage(message: message) } } // Concrete implementations of MessageSender protocol for different channels class EmailSender: MessageSender { func sendMessage(message: String) { print("Sending email: \(message)") } } class SMSSender: MessageSender { func sendMessage(message: String) { print("Sending SMS: \(message)") } } class PushNotificationSender: MessageSender { func sendMessage(message: String) { print("Sending push notification: \(message)") } } // Example usage let emailSender = EmailSender() let smsSender = SMSSender() let pushNotificationSender = PushNotificationSender() let emailService = MessageService(messageSender: emailSender) let smsService = MessageService(messageSender: smsSender) let pushNotificationService = MessageService(messageSender: pushNotificationSender) emailService.sendMessage(message: "Hello via email") smsService.sendMessage(message: "Hello via SMS") pushNotificationService.sendMessage(message: "Hello via push notification") Thank you for reading! 😊

March 5, 2024 · 2 min · Dmytro Chumakov

Big O notation

What is a Big O notation? The Big O notation helps identify algorithm efficiency. It can measure computation and memory growth with respect to input. Real-world code example O(n) — Linear Time func containsValue(array: [Int], value: Int) -> Bool { for element in array { if element == value { return true } } return false } O(1) — Constant Time func findFirstElement(array: [Int]) -> Int? { return array.first } Thank you for reading!

February 21, 2024 · 1 min · Dmytro Chumakov

Combine — Basics

What is Combine? Combine Framework provides an API for processing async events over time such as user-input, network response, and other dynamic data. What is the purpose of Combine? The purpose of Combine is to simplify the management of async events and data streams. Publishers Publisher declares that a type can transit a sequence of values over time. A publisher delivers elements to one or more Subscriber instances. class PostService { func fetchPosts() -> AnyPublisher<[Post], Error> { guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { fatalError("Invalid URL") } return URLSession.shared.dataTaskPublisher(for: url) .map(\.data) .decode(type: [Post].self, decoder: JSONDecoder()) .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } } Subscribers Subscriber is a protocol that declares a type that can receive input from a publisher. A Subscriber instance receives a stream of elements from a Publisher. ...

February 7, 2024 · 4 min · Dmytro Chumakov