Design Patterns in Swift: Chain of Responsibility Explored

Design Patterns in Swift: Chain of Responsibility Explored

Swift is becoming increasingly popular as a language for developing iOS applications. With its powerful type-checking capabilities, easy-to-read syntax, and support for object-oriented programming, it’s no surprise that developers are drawn to it. As with any programming language, there are a number of design patterns that can be used to structure your code. One such pattern is the Chain of Responsibility pattern. In this article, we will explore how to use this pattern in Swift.

The Chain of Responsibility pattern is a behavioral design pattern in which a series of objects, known as handlers, are linked together to handle a request. The request is passed from one handler to the next until it is handled. This pattern allows for a greater level of flexibility than other approaches, such as the Command Pattern, because it allows you to easily add or remove handlers from the chain without having to modify the code.

In Swift, the Chain of Responsibility pattern can be implemented using the Protocol-Oriented Programming paradigm. Here, the handlers are defined as protocols, which are then used to create concrete implementations of those handlers. This approach provides a great deal of flexibility, as adding or removing a handler simply requires creating a new protocol and implementing it.

Let’s take a look at an example of the Chain of Responsibility pattern in action. We’ll create a simple application that handles requests for discounts. We’ll start by defining the DiscountRequest protocol. This protocol defines a single method, calculateDiscount(), which takes a DiscountRequestData object and returns a Double. This method will be implemented by each handler in the chain.

protocol DiscountRequest {
    func calculateDiscount(discountRequestData: DiscountRequestData) -> Double
}

Next, we’ll define the DiscountRequestData struct. This struct will contain the data needed to make a discount request, such as the customer’s name and the amount of the purchase.

struct DiscountRequestData {
    let customerName: String
    let purchaseAmount: Double
}

Now that we have our protocols and data structures in place, we can start creating our handlers. Let’s start with the most basic handler, the DefaultDiscountHandler. This handler will simply return 0.0 as the discount amount, meaning that no discount will be applied.

class DefaultDiscountHandler: DiscountRequest {
    func calculateDiscount(discountRequestData: DiscountRequestData) -> Double {
        return 0.0
    }
}

Next, we’ll create a handler for customers who have made more than $1000 in purchases. This handler will return 10% of the purchase amount as the discount.

class HighValueCustomerDiscountHandler: DiscountRequest {
    func calculateDiscount(discountRequestData: DiscountRequestData) -> Double {
        if discountRequestData.purchaseAmount > 1000.0 {
            return discountRequestData.purchaseAmount * 0.1
        } else {
            return 0.0
        }
    }
}

Finally, we’ll create a handler for returning customers. This handler will return 20% of the purchase amount as the discount.

class ReturningCustomerDiscountHandler: DiscountRequest {
    func calculateDiscount(discountRequestData: DiscountRequestData) -> Double {
        if discountRequestData.customerName == "John Doe" {
            return discountRequestData.purchaseAmount * 0.2
        } else {
            return 0.0
        }
    }
}

With our handlers defined, we can now create our chain of responsibility. To do this, we’ll create a DiscountHandlerChain class, which will be responsible for creating and managing the chain.

class DiscountHandlerChain {
    private var handlers: [DiscountRequest] = []
    
    func addHandler(_ handler: DiscountRequest) {
        handlers.append(handler)
    }
    
    func handle(discountRequestData: DiscountRequestData) -> Double {
        var discount = 0.0
        
        for handler in handlers {
            discount += handler.calculateDiscount(discountRequestData: discountRequestData)
        }
        
        return discount
    }
}

The DiscountHandlerChain class has two methods: addHandler() and handle(). The addHandler() method adds a handler to the chain, and the handle() method is responsible for passing the request to each handler in the chain and accumulating the discount amount.

Finally, we can use our DiscountHandlerChain class to create our chain of handlers.

let handlerChain = DiscountHandlerChain()
handlerChain.addHandler(DefaultDiscountHandler())
handlerChain.addHandler(HighValueCustomerDiscountHandler())
handlerChain.addHandler(ReturningCustomerDiscountHandler())

let discountRequestData = DiscountRequestData(customerName: "John Doe", purchaseAmount: 1200.0)
let discount = handlerChain.handle(discountRequestData: discountRequestData)

print("Discount: \(discount)") // prints "Discount: 120.0"

In this example, we created a chain of three handlers: the DefaultDiscountHandler, the HighValueCustomerDiscountHandler, and the ReturningCustomerDiscountHandler. When we passed our DiscountRequestData object to the handler chain, the first handler (the DefaultDiscountHandler) returned 0.0 as the discount amount. The second handler (the HighValueCustomerDiscountHandler) then returned 10% of the purchase amount as the discount. Finally, the third handler (the ReturningCustomerDiscountHandler) returned an additional 20% of the purchase amount as the discount, resulting in a total discount of 120.0.

As you can see, the Chain of Responsibility pattern is a powerful and flexible way to structure your code. It allows you to easily add or remove handlers from the chain without having to modify the existing code. Furthermore, by using Protocol-Oriented Programming, you can create a highly extensible system that can be easily adapted to meet your needs.

If you’re looking for a way to structure your code in a more flexible way, the Chain of Responsibility pattern is definitely worth exploring. With its ability to easily add or remove handlers from the chain, it can provide the flexibility you need to create a highly extensible system.

Scroll to Top