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.

Animation showing numerous if-else statements being replaced by the word 'SOLID', which is then crossed out with a red X, indicating the violation of SOLID principles.

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.

Animated GIF demonstrating the Strategy Pattern in Java for adaptive rendering across desktop, tablet, and phone devices on a media platform.

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:

packageValueisExpressdistance
100$True3 miles
http://localhost:8080/delivery?packageValue=100&isExpress=True&distance=3

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.

packageValueisExpressdistance
100$False1 mile
http://localhost:8080/delivery?packageValue=100&isExpress=False&distance=1

"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!

Categorized in:

Design Patterns, Java,

Last Update: February 11, 2024