Strategy pattern java spring

Spring Boot: Strategy Design Pattern — Convenience and Limitations

Join the DZone community and get the full member experience.

You might have already used the strategy pattern in relationship with Spring Boot where it is very convenient to use. You simply define an interface for example (I use the prefixing I only in these examples for clarity):

public interface IOneStrategy 

Define some implementations like this:

public class OneStrategyFirst implements IOneStrategy 
 public void executeTheThing() 
 System.out.println("OneStrategyFirst.executeTheThing");

Now, you can simply implement a service, which will execute the appropriate strategy based on the given name which looks similar like this:

public class ExecuteStrategyOne 
 private MapString, IOneStrategy> strategies;
 public ExecuteStrategyOne(MapString, IOneStrategy> strategies) 
 this.strategies = strategies;
 public void executeStrategyOne(String name) 
 if (!strategies.containsKey(name)) 
 throw new IllegalArgumentException("The strategy " + name + " does not exist.");
 strategies.get(name).executeTheThing();

In the real world, you make several implementations of the strategy interface like OneStrategyFirst , OneStrategySecond , and OneStrategyThird . Sometimes, the usage is to use the parameter of executeStrategyOne , which is provided by a REST API or some other domain specific code which needs different implementations.

The convenience here is that Spring Boot (Spring Framework to be more accurate) handles the injection of the different implementation into the strategies Map within ExecuteStrategyOne via the constructor. This results in a Map where the key is the value which is given by @Service(«FIRST») and the value of the map contains an instantiates class of every implementation of the interface IOneStrategy which can be found.

In real life, it happens that you need to have a different strategy which use the same keys as FIRST , SECOND , and THIRD in the examples? Let us define the following:

Источник

Strategy Pattern with the Spring Framework

The Spring Framework (and its extension — Spring Boot) allows us to write Java code quicker, easier, and safer. It’s no wonder, then, that through its dependency injection features it can make our lives simpler when implementing well-known design patterns like the Strategy Pattern. There are multiple ways we could implement this with Spring — in this article I aim to showcase the simplest and most effective way I believe can be applied in the majority of use cases. Note: this article uses code written in Java 17, but equivalent solutions can be achieved with earlier versions.

Using DI in the Strategy Pattern

Context

  • an ExportFormat enum containing the supported formats
  • an ExportFile record class representing the file
  • an Exporter interface as a blueprint for our services
public enum ExportFormat < JSON, CSV, XML >public record ExportFile(String name, byte[] content) <> public interface Exporter < ExportFormat getType(); ExportFile export(UUID id); >public class JsonExporter implements Exporter < @Override public ExportFormat getType() < return ExportFormat.JSON; >// other code omitted. > public class CsvExporter implements Exporter <> public class XmlExporter implements Exporter <> 

The Traditional Way

The most basic way to implement the Strategy Pattern would be to use another creational pattern to create our strategies — the Factory Pattern:

public class ExporterFactory < public Exporter create(final ExportFormat format) < return switch(format) < case JSON ->new JsonExporter(); case CSV -> new CsvExporter(); case XML -> new XmlExporter(); >; > > 

This approach works in many simple implementations, but quickly becomes less ideal when our services require other dependencies -> because we are instantiating our exporters here, we’d have to inject dependencies into the ExporterFactory and pass them to our exporters. Whenever we add or remove dependencies, we’d have to make changes to both the factory and our exporters (and associated test code).

The Spring DI Way

Instead of instantiating our exporters manually, we can annotate them with @Component or @Service, which allows us to inject them in other classes.

Because our services extend the Exporter interface, Spring can provide all of them in the form of a List or Set . All we need to do is create a resolver instead of a factory to use the correct exporter. This class will retrieve the right exporter from the Set based on the given format:

@Service public class ExporterResolver < private final Setexporters; public ExporterResolver(final Set exporters) < this.exporters = exporters; >public Exporter create(final ExportFormat format) < return exporters.stream() .filter(e ->e.getType() == format) .findFirst() .orElseThrow(IllegalArgumentException::new); > > 

Putting Things Together

Lastly, our service will use the resolver to perform the task at hand:

@Service public class ExportService < private final ExporterResolver resolver; public ExportService(final ExporterResolver resolver) < this.resolver = resolver; >public ExportFile export(final ExportFormat format, final UUID id) < return resolver.create(format).export(id); >> 

Источник

Introduction

Strategy pattern is a behavior design pattern which allows us to select the strategy at runtime. This is particularly useful in the context where the application determines the strategy to run based on user input/selection.

Traditionally, in a strategy pattern implementation, we would have to use a Context which acts as a middleman between the client and the strategy executor . However, in the case of Spring, the implementation is much simpler as there is no need for a Context in order for this to work, which makes the implementation much more elegant.

Demo

In this demo, we will write a less efficient implementation first, and then attempt to enhance it.

Imagine that we have an API that provides flight information.

@RestController public class FlightController < @GetMapping("/flights/") public String getFlightInfo(@PathVariable("airline") String airline) < // omitted > > 
public interface FlightInfo < String display(); > 

Where different airline company would implement this interface, like such

@Service public class SingaporeAir implements FlightInfo < @Override public String display() < return "Singapore Airlines"; > > 

Now, let’s go back to our controller and finish up the implementation

@RestController @RequiredArgsConstructor public class FlightController < private final SingaporeAir singaporeAir; // 1 private final ThaiAir thaiAir; // 1 @GetMapping("/flights/") public String getFlightInfo(@PathVariable("airline") String airline) < if ("ThaiAir".equals(airline)) < // 2 return this.thaiAir.display(); // 3 > if ("SingaporeAir".equals(airline)) < // 2 return this.singaporeAir.display(); // 3 > return "N.A"; > > 

You probably will end up with something like the above, where

  1. we inject the different classes that implement FlightInfo interface
  2. match against the input
  3. select the correct implementation to return the result

Let’s try to hit against the API, and see the output

Excellent, we have done our job, and let’s call it a day. But, on the next day, we need to add support to provide more airline information. With the current implementation, it’s not going to be maintainable in the long run, where we would either end up with lots of if statements, or switch case statement. And that is not ideal where with each new implementation, we would need to make changes to the original code.

This is where we can enhance our current implementation.

Firstly, let’s make some tweaks to some existing classes.

@Service("SingaporeAir") public class SingaporeAir implements FlightInfo < // omitted, no change from before > @Service("ThaiAir") public class ThaiAir implements FlightInfo < // omitted, no change from before > 

We added the beanName so to easily match our bean against the input later on.

@RestController @RequiredArgsConstructor // 1 public class FlightController < private final Map flightInfoMap; //2 @GetMapping("/flights/") public String getFlightInfo(@PathVariable("airline") String airline) < FlightInfo flightInfo = this.flightInfoMap.get(airline); // 3 if (flightInfo != null) < return flightInfo.display(); // 4 > return "N.A"; > > 
  1. This is a Lombok annotation to help create the constructor, don’t worry about this. It is not critical to the implementation, and you can still use the handwritten constructor if you like
  2. With autowiring-by-type, Spring allows us to inject via a List or Map . In this case, we are using Map and the key would be the beanName which we declared previously
  3. We assume the input would match the beanName , and thus, extract the correct service from the Map
  4. And if we are able to extract, we would then call the display method to grab the value

Notice that now, we are only injecting a Map of FlightInfo instead of injecting the individual service(s). This way, when we introduce more airlines that implements FlightInfo , there would be no change to the existing code.

@Service("Emirates") public class Emirates implements FlightInfo < // omitted > 

Conclusion

We looked at how we implemented Strategy Pattern with Spring Boot using a less maintainable way, and then enhanced the implementation by injecting a Map which allow us to look up the respective implementation and trigger the correct method call.

Source Code

As usual, full source code is available in GitHub

References

Источник

Strategy & Factory Patterns in Spring Boot Application: Part I

Strategy and Factory patterns are the most useful design patterns in any application. If you have a branching code with lots of ‘if and else’, then the Strategy pattern is the best choice. With Spring Boot’s powerful dependency injection, Factory pattern comes to the rescue when different subtypes of objects are needed.

Let’s consider an example: you’re building a Report Microservice using Spring Boot. For this example, assume a REST controller with a GET endpoint that will provide data for different types of Report requests. Instead of using lots of ‘if and else’ for different chart requests, I’m going to demonstrate how a combination of Factory and Strategy patterns is a great fit.

REST Controller

The screenshot below shows the ReportController code. The Controller has one endpoint for ‘Engine’ category and another endpoint for ‘ESS’ category.

Spring Boot Application

Report Service

Report Service is a component that services various categories of reports. As you can see, I have one factory class for each Category. The call from the Service invokes the factory method to get the appropriate report data with reportId, and userId has the inputs.

Strategy and Factory Patterns in a Spring Boot Application – Part 1

Factory pattern and Strategy pattern package structure

Strategy and Factory Patterns in a Spring Boot Application – Part 1

Factory Pattern

Factory Pattern is a creational pattern and is one of the most widely used patterns in many frameworks.

Report Id can be a String or an Integer. However, including an Enum for Report Id will be best for Type safety. The screenshot below displays Enum for Engine Report, one each for one report Id.

Strategy and Factory Patterns in a Spring Boot Application – Part 1

The screenshot below shows a Factory class that includes a Map containing service classes: one each for one report Id. Report Id Enum is used as the Key for the map to associate a report Id with a Service class for a Report Id.

Traditional Java applications use new operators to create Service instances. However, since this is a Spring Boot application, Spring framework scans the Service classes of a certain type and populates the Map during startup.

Strategy and Factory Patterns in a Spring Boot Application – Part 1

Conclusion

This initial post (in a two-part blog series) explained Factory pattern usage in a Spring Boot application. Part 2 of the series will further explore how Strategy pattern is applied alongside Factory Pattern. Stay tuned! In the meantime, if you have any questions about Factory and Strategy patterns that need immediate attention, please don’t hesitate to reach out to us for answers.

By Verinext | 2023-03-20T19:24:47+00:00 January 22nd, 2021 | Categories: Blog | Tags: Code, Spring Boot Application, strategy pattern | Comments Off on Strategy & Factory Patterns in Spring Boot Application: Part I

Источник

Читайте также:  Remove file with python
Оцените статью