Greetings, traveler!
We continue to analyze Design Patterns, and today, we will focus on the latest Structural Pattern, a Proxy.
A Proxy is used in different situations. For instance, you might have a service that you only want to start under specific conditions. In this case, you could use a Proxy to check if the conditions are met before allowing the service to start.
But a Proxy’s versatility doesn’t end there. It can also log or cache data or check conditions for running specific functions.
Example of usage
Let’s look at some usage examples. For instance, you have a network service and want to run a query multiple times. However, you also want to be able to cancel a previous request with the same label. To do this, we can use a proxy.
protocol DownloadManagerProtocol {
func download(url: URL) -> URLSessionDownloadTask
}
final class DownloadManager: DownloadManagerProtocol {
@discardableResult
func download(url: URL) -> URLSessionDownloadTask {
let task = URLSession
.shared
.downloadTask(with: URLRequest(url: url))
task.resume()
return task
}
}
final class DownloadManagerProxy: DownloadManagerProtocol {
private let manager: DownloadManagerProtocol
private var cache: [URL: URLSessionDownloadTask] = [:]
init(manager: DownloadManagerProtocol) {
self.manager = manager
}
@discardableResult
func download(url: URL) -> URLSessionDownloadTask {
if let task = cache[url] {
task.cancel()
}
let newTask = manager.download(url: url)
cache[url] = newTask
return newTask
}
}
Another example could be restricting access to certain features or actions if specific conditions are not fulfilled. Here is an example of how this might work in practice.
We can only provide certain features to those users who have purchased a premium subscription.
struct User {
let isPremium: Bool
}
protocol DisplayProtocol {
func display()
}
final class DisplayManager: DisplayProtocol {
func display() {
print("display premium features")
}
}
final class DisplayManagerProxy: DisplayProtocol {
private let user: User
private let manager: DisplayManager
init(user: User, manager: DisplayManager) {
self.user = user
self.manager = manager
}
func display() {
guard user.isPremium else {
print("Wanna buy premium?")
return
}
manager.display()
}
}
Conclusion
Of course, there are many more great examples, but I think the overall idea is clear, so let’s wrap up this discussion of the Proxy design pattern and move on to the next topic, Behavioral Design Patterns. The first one we will discuss is the Chain of Responsibility pattern. See you in the following article.
Check out other posts in the Design Patterns series:
- Visitor Design Pattern in Swift
- Themplate Method Design Pattern in Swift
- Strategy Design Pattern in Swift
- State Design Pattern in Swift
- Observer Design Pattern in Swift
- Memento Design Pattern in Swift
- Mediator Design Pattern in Swift
- Iterator Design Pattern in Swift
- Command Design Pattern in Swift
- Chain of Responsibility Design Pattern in Swift
- FlyWeight Design Pattern in Swift
- Facade Design Pattern in Swift
- Decorator Design Pattern in Swift
- Composite Design Pattern in Swift
- Bridge Design Pattern in Swift
- Adapter Design Pattern in Swift
- Singleton Design Pattern in Swift
- Prototype Design Pattern in Swift
- Builder Design Pattern in Swift
- Abstract Factory Design Pattern in Swift
- Factory Method Design Pattern in Swift
- Design Patterns: Basics