The Strategy pattern in Java stands out as a robust design pattern that enables objects to choose from a variety of algorithms dynamically.
it’s a prime example of a Behavioral design pattern. This category of design patterns focuses on improving communication and the assignment of responsibilities between objects, making it particularly useful in scenarios where you need to alter the behavior of an application dynamically without extending it.
It’s particularly useful in scenarios where you need to alter the behavior of an application without extending it.
This pattern is essential for maintaining clean code, promoting the SOLID principles, and ensuring your application remains scalable and easy to maintain.
Keep reading if you want to find out how we’ve integrated F1 and Max Verstappen into this equation. 🏎️
Understanding the Strategy Pattern
At its core, the Strategy pattern is about choices – specifically, providing an object the ability to choose from a family of algorithms at runtime.
Imagine navigating a complex web of ‘if-else’ statements to handle various behaviors within your application.
Not only does this approach clutter your code, but it also makes it a nightmare to maintain.
The Strategy pattern elegantly solves this problem by encapsulating each algorithm in separate classes, all implementing a common interface. This design not only cleans up your code but also aligns perfectly with the SOLID principles, promoting more robust, maintainable, and flexible code architecture.
A Practical Example: Optimizing Content Delivery
Consider an online media platform designed to deliver content seamlessly across diverse devices – desktops, smartphones, and tablets.
Each device has unique capabilities, requiring different content rendering strategies to ensure an optimal user experience.
Implementing the Strategy pattern allows the platform to dynamically select the most appropriate content rendering strategy based on the device’s characteristics.
In this context, the Strategy pattern shines by allowing the separation of the content rendering logic into distinct strategies. This separation enables easy adaptation to new device types or changing requirements without altering the core application logic.
Delivery Management System using Strategy Pattern in Java
To illustrate the practical application of the Strategy pattern, let’s develop a simplified delivery system. The system dynamically selects the optimal delivery method based on user preferences, package value, express requirements, and distance.
Implementing Strategy pattern in Java & Spring Boot
The implementation involves defining the Strategy interface (DeliverStrategy
) and concrete strategies (StandardDelivery
, ExpressDelivery
, and DroneDelivery
).
DeliverStrategy
interface
package com.nemanjatanaskovic.delivery.strategy;
public interface DeliverStrategy {
String deliver(
int packageValue,
boolean isExpress,
double distance
);
}
StandardDelivery
– This option is available in almost all delivery management apps and will be the default choice if we don’t find a better fit for our users.
package com.nemanjatanaskovic.delivery.strategy;
import org.springframework.stereotype.Component;
@Component
public class StandardDelivery implements DeliverStrategy {
@Override
public String deliver(int packageValue, boolean isExpress, double distance) {
return "Standard delivery activated...";
// all the logic on selecting the appropriate vehicle and driver
}
}
ExpressDelivery
– Express Delivery – This option is designed for users willing to pay extra for the fastest delivery, ensuring their package arrives with the speed of Max Verstappen at the start of an F1 race. 🏁
package com.nemanjatanaskovic.delivery.strategy;
import org.springframework.stereotype.Component;
@Component
public class ExpressDelivery implements DeliverStrategy {
@Override
public String deliver(int packageValue, boolean isExpress, double distance) {
return "F1 activated for an Express delivery!";
// all the logic on selecting the appropriate F1 vehicle and driver
}
}
- Finally, there’s
DroneDelivery
. This option is utilized when the distance is less than 2 miles. We are particularly fond of it because it is cost-effective, thanks to its battery operation.
package com.nemanjatanaskovic.delivery.strategy;
import org.springframework.stereotype.Component;
@Component
public class DroneDelivery implements DeliverStrategy {
@Override
public String deliver(int packageValue, boolean isExpress, double distance) {
return "Drone delivery activated!";
// all the logic on selecting the best drone for delivery
}
}
Now in our DeliverService
let’s set some ground rules on which strategy should be executed.
package com.nemanjatanaskovic.delivery.service;
import com.nemanjatanaskovic.delivery.strategy.DeliverStrategy;
import com.nemanjatanaskovic.delivery.strategy.DroneDelivery;
import com.nemanjatanaskovic.delivery.strategy.ExpressDelivery;
import com.nemanjatanaskovic.delivery.strategy.StandardDelivery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeliverService {
@Autowired
private StandardDelivery standardDelivery;
@Autowired
private ExpressDelivery expressDelivery;
@Autowired
private DroneDelivery droneDelivery;
public String deliverPackage(
int packageAmount,
boolean isExpress,
double distance
) {
DeliverStrategy strategy = standardDelivery;
if (distance < 2.0) {
strategy = droneDelivery;
}
if (isExpress) {
strategy = expressDelivery;
}
return strategy.deliver(
packageAmount,
isExpress,
distance
);
}
}
As we can see from the example, if the distance
is less than 2 miles, we'll activate DroneDelivery
.
On the other hand, if someone really wants to meet Max and sets the isExpress
flag to True
, then we need to respect that and activate the ExpressDelivery
strategy.
If none of these two are matched, we'll just default to standard delivery (boooring 🥱).
Finally, let's create DeliveryController
so we can test our implementation using Postman.
package com.nemanjatanaskovic.delivery.controller;
import com.nemanjatanaskovic.delivery.service.DeliverService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/delivery")
public class DeliveryController {
@Autowired
private DeliverService deliverService;
@GetMapping("")
public ResponseEntity deliverPackage(
@RequestParam int packageValue,
@RequestParam boolean isExpress,
@RequestParam double distance
) {
String response = deliverService.deliverPackage(
packageValue, isExpress, distance
);
return ResponseEntity.ok(response);
}
}
Testing
Now, let's test our Back-End with the following parameters:
packageValue | isExpress | distance |
100$ | True | 3 miles |
Expected response:
"F1 activated for an Express delivery!"
Success! It works, woohoo!
Just for fun, let's run another test by calling the endpoint with isExpress
set to False
and distance
of only one mile.
packageValue | isExpress | distance |
100$ | False | 1 mile |
"Drone delivery activated!"
Confirmed! 👌
Speaking of patterns, ever wondered how weather apps keep up with Mother Nature's mood swings? Check out my whirlwind adventure through the Observer Pattern in Weather App: Mastering the Observer Pattern 🌧️
Conclusion
So, we've seen how the Strategy pattern can make our delivery system as adaptable and efficient as an F1 team tweaking their race plan.
Pretty cool, right?
If you're keen to see all the code magic up close or want to chip in with your own genius, check out my GitHub repository.
Let's keep our code and our projects as thrilling and on-point as a Grand Prix winner!
Happy coding!