From 57ebef58841bd76b9b2ffd4296e55a730fbda87a Mon Sep 17 00:00:00 2001 From: Shaun Culver Date: Tue, 3 Dec 2024 09:05:15 +0200 Subject: [PATCH 1/3] Chain of Responsibility --- .../AppleStoreChainOfResponsibility/README.md | 145 ++++++++++-------- 1 file changed, 82 insertions(+), 63 deletions(-) diff --git a/DesignPatterns/AppleStoreChainOfResponsibility/README.md b/DesignPatterns/AppleStoreChainOfResponsibility/README.md index b9f6aa2..c205b85 100644 --- a/DesignPatterns/AppleStoreChainOfResponsibility/README.md +++ b/DesignPatterns/AppleStoreChainOfResponsibility/README.md @@ -10,105 +10,124 @@ ## Pattern overview -- The Chain of Responsibility pattern is a behavioral pattern that allows more than one object to handle a request. -- The pattern chains the receiving objects and passes the request along the chain until an object handles it. -- For example, if an app home screen has multiple components, a sequence of service calls may be required to load the screen. These can be chained together using the pattern. -- In addition to being an efficient way to handle a sequence of steps, the pattern allows for each one to be customised according to the requirements. -- The request passed through the chain can be modified by each step in the chain. +- The Chain of Responsibility pattern provides a way of linking a series of actions. + +- Each action has a task to perform, and once it's completed, the request is passed to the next action in the chain. + +- This pattern allows for a clear separation of concerns and flexibility in the order of actions. ## Problem statement -- When adding an item to the bag, we would like to perform an inventory check if it's not a preordered item. -- We would also like to check if the current session is valid. -- The two steps are required to be performed in sequence. +- When users add a product to their bag, a series actions need to be performed. + +- For example, checking if the product is in stock, attempting to add it to the bag, and logging an analytics event. + +- Handling all of these actions in a single class is possible, but can lead to coupling and a lack of flexibility. + +- The Chain of Responsibility pattern can be used to create a chain of handlers, each responsible for a specific action. + +- Each handler performs its specific action and then passes the request to the next handler in the chain. + +- This enabled a clear separation of concerns and allows for flexibility to add, remove, or reorder the handlers in the chain. + +## Definitions -## Domain application +### Handler: -Handler: +- Defines the interface for handling requests and setting up the chain. -- Defines an interface for handling requests. -- Implements the successor link. (Optional) +- We're using a closure to communicate the result of the request. + +- The `Request` object can be used at any point in the chain. ```swift protocol Handler { - var nextHandler: (any Handler)? { get set } - func handle(request: Request) async -> Result + var nextHandler: Handler? { get } + + func handleRequest(_ request: Request, completion: @escaping (String) -> Void) +} + +struct Request { + let productId: String } ``` -ConcreteHandler: +### Concrete handlers: - Handles requests it is responsible for. -- Can access its successor. -- If the ConcreteHandler can handle the request, it does so; otherwise it forwards the request to its successor. + +- Maintains a reference to the next handler in the chain. + +- In this chain, before adding the product to the bag, the stock check handler checks if the product is in stock. + +- If it's in stock, then the add to bag handler is called. + +- Assuming it gets added to the bag, the logging handler is called, completing the chain. + +- This approach allows for flexibility in the order of the handlers while keeping a clear separation of concerns. ```swift -class InventoryCheckHandler: Handler { - var inventoryService: InventoryService - var nextHandler: (any Handler)? +class StockCheckHandler: Handler { + private(set) var nextHandler: Handler? - init(inventoryService: InventoryService, nextHandler: (any Handler)?) { - self.inventoryService = inventoryService + init(nextHandler: Handler?) { self.nextHandler = nextHandler } - func handle(request: Request) async -> Result { - if request.isPreorder { - // No inventory check required for preordered items - return await nextHandler?.handle(request: request) ?? .success(true) - } - else { - let result = await inventoryService.checkIfProductIsInStock(productId: request.productId) - - switch result { - case .success: - return await nextHandler?.handle(request: request) ?? .success(true) - case .failure(let error): - return .failure(error) - } + func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + let isInStock = Bool.random() + + if isInStock { + completion("Product is in stock") + nextHandler?.handleRequest(request, completion: completion) + } else { + completion("Product \(request.productId) is out of stock") } } } -``` -```swift -class AuthenticationHandler: Handler { - var authenticationService: AuthenticationService - var nextHandler: (any Handler)? +class AddToBagHandler: Handler { + private(set) var nextHandler: Handler? - init(authenticationService: AuthenticationService, nextHandler: (any Handler)?) { - self.authenticationService = authenticationService + init(nextHandler: Handler?) { self.nextHandler = nextHandler } - func handle(request: Request) async -> Result { - let result = await authenticationService.checkIfSessionIsValid() + func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + let addToBagSucceeded = Bool.random() - switch result { - case .success: - return await nextHandler?.handle(request: request) ?? .success(true) - case .failure(let error): - return .failure(error) + if addToBagSucceeded { + completion("Product \(request.productId) added to bag") + nextHandler?.handleRequest(request, completion: completion) + } else { + completion("Failed to add product \(request.productId) to bag") } } } -``` - -Client: - -Initiates the request to a ConcreteHandler object on the chain. -```swift -class BagService { - let handler: Handler +class LoggingHandler: Handler { + private(set) var nextHandler: Handler? - init(handler: Handler) { - self.handler = handler + init(nextHandler: Handler?) { + self.nextHandler = nextHandler } - func addProductToBag(productId: String, isPreorder: Bool, isAuthenticated: Bool) async -> Result { - let request = Request(productId: productId, isPreorder: isPreorder, isAuthenticated: isAuthenticated) - return await handler.handle(request: request) + func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + completion("Analytics event logged for product \(request.productId)") + completion("Request completed") } } ``` + +## Example + +```swift +let loggingHandler = LoggingHandler(nextHandler: nil) +let addToBagHandler = AddToBagHandler(nextHandler: loggingHandler) +let stockCheckHandler = StockCheckHandler(nextHandler: addToBagHandler) + +let request = Request(productId: "1234") +stockCheckHandler.handleRequest(request) { result in + print(result) +} +``` From de5b0dfaf89736ce2f948fdfe7349145988a0807 Mon Sep 17 00:00:00 2001 From: Shaun Culver Date: Tue, 3 Dec 2024 09:15:38 +0200 Subject: [PATCH 2/3] Chain of Responsibility refinements --- .../AppleStoreChainOfResponsibility/README.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/DesignPatterns/AppleStoreChainOfResponsibility/README.md b/DesignPatterns/AppleStoreChainOfResponsibility/README.md index c205b85..eca74ce 100644 --- a/DesignPatterns/AppleStoreChainOfResponsibility/README.md +++ b/DesignPatterns/AppleStoreChainOfResponsibility/README.md @@ -42,9 +42,9 @@ ```swift protocol Handler { - var nextHandler: Handler? { get } + var next: Handler? { get } - func handleRequest(_ request: Request, completion: @escaping (String) -> Void) + func handle(request: Request, completion: @escaping (String) -> Void) } struct Request { @@ -68,18 +68,18 @@ struct Request { ```swift class StockCheckHandler: Handler { - private(set) var nextHandler: Handler? + private(set) var next: Handler? - init(nextHandler: Handler?) { - self.nextHandler = nextHandler + init(next: Handler?) { + self.next = next } - func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + func handle(request: Request, completion: @escaping (String) -> Void) { let isInStock = Bool.random() if isInStock { - completion("Product is in stock") - nextHandler?.handleRequest(request, completion: completion) + completion("Product \(request.productId) is in stock") + next?.handle(request: request, completion: completion) } else { completion("Product \(request.productId) is out of stock") } @@ -87,18 +87,18 @@ class StockCheckHandler: Handler { } class AddToBagHandler: Handler { - private(set) var nextHandler: Handler? + private(set) var next: Handler? - init(nextHandler: Handler?) { - self.nextHandler = nextHandler + init(next: Handler?) { + self.next = next } - func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + func handle(request: Request, completion: @escaping (String) -> Void) { let addToBagSucceeded = Bool.random() if addToBagSucceeded { completion("Product \(request.productId) added to bag") - nextHandler?.handleRequest(request, completion: completion) + next?.handle(request: request, completion: completion) } else { completion("Failed to add product \(request.productId) to bag") } @@ -106,13 +106,13 @@ class AddToBagHandler: Handler { } class LoggingHandler: Handler { - private(set) var nextHandler: Handler? + private(set) var next: Handler? - init(nextHandler: Handler?) { - self.nextHandler = nextHandler + init(next: Handler?) { + self.next = next } - func handleRequest(_ request: Request, completion: @escaping (String) -> Void) { + func handle(request: Request, completion: @escaping (String) -> Void) { completion("Analytics event logged for product \(request.productId)") completion("Request completed") } @@ -122,12 +122,12 @@ class LoggingHandler: Handler { ## Example ```swift -let loggingHandler = LoggingHandler(nextHandler: nil) -let addToBagHandler = AddToBagHandler(nextHandler: loggingHandler) -let stockCheckHandler = StockCheckHandler(nextHandler: addToBagHandler) +let loggingHandler = LoggingHandler(next: nil) +let addToBagHandler = AddToBagHandler(next: loggingHandler) +let stockCheckHandler = StockCheckHandler(next: addToBagHandler) let request = Request(productId: "1234") -stockCheckHandler.handleRequest(request) { result in +stockCheckHandler.handle(request: request) { result in print(result) } ``` From 95c10f60b031178ee0cbdc9680c0e407b4123c84 Mon Sep 17 00:00:00 2001 From: Shaun Culver Date: Mon, 6 Jan 2025 08:26:56 +0200 Subject: [PATCH 3/3] Chain of Responsibility updates --- .../AppleStoreChainOfResponsibility/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/DesignPatterns/AppleStoreChainOfResponsibility/README.md b/DesignPatterns/AppleStoreChainOfResponsibility/README.md index eca74ce..853c9a0 100644 --- a/DesignPatterns/AppleStoreChainOfResponsibility/README.md +++ b/DesignPatterns/AppleStoreChainOfResponsibility/README.md @@ -18,7 +18,7 @@ ## Problem statement -- When users add a product to their bag, a series actions need to be performed. +- When users add a product to their bag, a series of actions need to be performed. - For example, checking if the product is in stock, attempting to add it to the bag, and logging an analytics event. @@ -28,7 +28,7 @@ - Each handler performs its specific action and then passes the request to the next handler in the chain. -- This enabled a clear separation of concerns and allows for flexibility to add, remove, or reorder the handlers in the chain. +- This enables a clear separation of concerns and allows for flexibility to add, remove, or reorder the handlers in the chain. ## Definitions @@ -40,6 +40,8 @@ - The `Request` object can be used at any point in the chain. +- The `next` property is used to maintain a reference to the next handler in the chain. + ```swift protocol Handler { var next: Handler? { get } @@ -114,7 +116,6 @@ class LoggingHandler: Handler { func handle(request: Request, completion: @escaping (String) -> Void) { completion("Analytics event logged for product \(request.productId)") - completion("Request completed") } } ``` @@ -130,4 +131,9 @@ let request = Request(productId: "1234") stockCheckHandler.handle(request: request) { result in print(result) } + +// Output: +// Product 1234 is in stock +// Product 1234 added to bag +// Analytics event logged for product 1234 ```