Strategy Design Pattern in Swift with Real-World Example
Introduction
Hi developers! In this article, we will deep into the strategy pattern in Swift. We will explore its core principles, see it in action through a real-world example, and discuss its benefits and potential drawbacks. Whether you’re building games, complex UI layouts, or dynamic data processing, this design pattern can bring agility and clarity to your code.
So, if you are ready to make your Swift code more versatile and easier to manage, let’s embark on this journey to explore the Strategy pattern together. Let’s get started.
Understanding the Strategy Pattern
The Strategy pattern is a behavioral design pattern that enables you to define a family of algorithms, encapsulate each one, and make them interchangeable. This pattern lets a client choose the appropriate algorithm at runtime, making it incredibly flexible and easy to extend.
Alright, let’s dive into the key components of the Strategy pattern:
- Context
Think of the “Context” as a class in your code that makes decisions. It needs to get stuff done but doesn’t want to do everything itself. It’s the boss who knows about different strategies but doesn’t get his hands dirty with the details. - Strategy (Interface/Protocol in Swift)
Now, the “Strategy” is like a game plan. It’s not an actual implementation but more like a set of rules or functions that everyone has to follow. It’s an interface/protocol in Swift or an abstract class. - Concrete strategies
The “Concrete Strategies” are the worker classes. They’re the ones that do the actual job. Each concrete strategy is a different way of getting things done, following the rules set by the game plan.
Benefits of The Strategy Pattern
- Flexibility
One of the primary advantages of the Strategy pattern is its flexibility. It allows you to switch between different algorithms or strategies at runtime. - Modularity
The pattern promotes modularity by encapsulating each algorithm or strategy in its own class - Extensibility
Adding new strategies is quite straightforward without modifying existing code, and also it promotes an open/closed principle. - Readability and Maintainability
The pattern enhances code readability by separating concerns and making each strategy-focused class easy to understand. - Adaptability to Change
The pattern allows your code to adapt to changing requirements or business rules more gracefully. - Encapsulation
Each strategy is encapsulated within its own class, preventing the spread of implementation details throughout the codebase.
Potential drawbacks
- Increased Number of Classes
Implementing the pattern often leads to an increase in the number of classes, with each strategy requiring its own class may result in a more complex class hierarchy. - Increased Complexity
In smaller projects or situations where the number of algorithms is limited, applying the pattern might introduce unnecessary complexity. - Potential for Code Duplication
If strategies share a common code, there is a risk of code duplication among the concrete strategy classes. This can also lead to maintainability issues, as changes in shared code might need to be replicated across multiple strategy classes.
Real-world example
Now, let’s put the Strategy pattern into action with a practical example in the world of logins. Think about how you log in to you favorite apps — maybe thorugh email, Google, Facebook, Twitter or another services. Each of these has its own rules and steps.
Imagine if we could make our app smart enough to handle all these logins smoothly, and even add more options without making things complicated. That’s where this pattern comes in. It’s making our authentication system versatile and ready for wahtever logins our users prefer.
In this example, we will create a authentication system that’s support login by email, one for Google, Facebook and so on. With the pattern, we’re making our authentication process more flexible and ready for anything. Let’s dive in!
// Strategy Interface
protocol AuthStrategy {
func authenticate(username: String, password: String) -> Bool
}
// Concrete Strategies
class EmailAuthStrategy: AuthStrategy {
func authenticate(username: String, password: String) -> Bool {
// Simulated email authentication logic
print("Authenticating via Email...")
return true
}
}
class GoogleAuthStrategy: AuthStrategy {
func authenticate(username: String, password: String) -> Bool {
// Simulated Google authentication logic
print("Authenticating via Google...")
return true
}
}
class FacebookAuthStrategy: AuthStrategy {
func authenticate(username: String, password: String) -> Bool {
// Simulated Google authentication logic
print("Authenticating via Facebook...")
return true
}
}
// Context Class
class AuthManager {
private var authStrategy: AuthStrategy
init(strategy: AuthStrategy) {
self.authStrategy = strategy
}
func setStrategy(strategy: AuthStrategy) {
self.authStrategy = strategy
}
func authenticateUser(username: String, password: String) -> Bool {
/*
You can put your pre-auth handling here
e.g., clear data, logs, etc.
*/
if !isNetworkConnected() {
print("Failed to authenticate. No network connection.")
return false
}
// Authenticate user using the selected strategy
let isAuthenticated = authStrategy.authenticate(username: username, password: password)
if isAuthenticated {
// Perform post-authentication actions
storeUserDataLocally()
navigateToHomePage()
/*
You can put your additional handling here
e.g., update user session, send notifications, etc.
*/
}
return isAuthenticated
}
}
Let’s deep dive into the details of the example:
- Strategy Interface (
AuthStrategy
)
TheAuthStrategy
protocol serves as the common ground for all authentication strategies. It declares theauthenticate
method, ensuring that each strategies conform to it. - Concrete Strategies (
EmailAuthStrategy
,GoogleAuthStrategy
,FacebookAuthStrategy
)
Each concrete strategy encapsulates the authentication logic specific to particular service. For instance,GoogleAuthStrategy
handles authentication via Google by using their SDK. This encapsulation ensures that the details of each authentication method are neatly contained within their respective classes. - Context (
AuthManager
)
TheAuthManager
class acts as the context that orchestrates the authentication process like the pre-auth and post-auth actions. It has reference to the current strategy (authStrategy
) and exposes a method (authenticateUser
) for the client to trigger authentication.
Client code usage:
class LoginViewModel {
var username: String = ""
var password: String = ""
let authManager = AuthManager(strategy: EmailAuthStrategy())
func signIn(using option: AuthOption) {
switch option {
case .facebook:
authManager.setStrategy(strategy: FacebookAuthStrategy())
case .google:
authManager.setStrategy(strategy: GoogleAuthStrategy())
case .email:
authManager.setStrategy(strategy: EmailAuthStrategy())
}
let isSuccess = authManager.authenticate(username: username, password: password)
// the rest of the authentication logic
}
}
So easy right?
This example provides a more comprehensive illustration of how an authentication manager can handle not only authentication but also maintain additional user-related information, enhancing the overall user management capabilities.
And if new requirement arises, such as adding “Sign-in with Apple” or any other authentication services, you can seamlessly extend your authentication system without distrupting the existing codebase.
Conclusion
In this exploration of the Strategy design pattern, we’ve uncovered a versatile and modular method for managing diverse authentication scenarios. This approach not only strengthens the reliability of our authentication systems but also streamlines the process of adding new authentication methods in the future. The Strategy pattern gives us the ability to adjust to evolving requirements without messing up the structure of our authentication manager.
Thank you for taking the time to explore the Strategy design pattern with me. If you have any thoughts, suggestions, or feedback, please share them in the comments. Your support, whether through claps or following, is greatly appreaciated. Stay tuned for more articles, and see you in the next one!🚀