DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • IntelliJ and Java Spring Microservices: Productivity Tips With GitHub Copilot
  • Composing Custom Annotations in Spring

Trending

  • Running LLMs Locally: A Step-by-Step Guide
  • Enhancing Secure Software Development With ASOC Platforms
  • Test Parameterization With JUnit 5.7: A Deep Dive Into @EnumSource
  • Effective Communication Strategies Between Microservices: Techniques and Real-World Examples
  1. DZone
  2. Coding
  3. Java
  4. Spring Strategy Pattern Example

Spring Strategy Pattern Example

In this tutorial, explore various Strategy pattern implementations in the Spring framework such as list injection, map injection, and method injection.

By 
Max Stepovyi user avatar
Max Stepovyi
·
Feb. 22, 24 · Tutorial
Like (11)
Save
Tweet
Share
7.8K Views

Join the DZone community and get the full member experience.

Join For Free

In this example, we'll learn about the Strategy pattern in Spring. We'll cover different ways to inject strategies, starting from a simple list-based approach to a more efficient map-based method. To illustrate the concept, we'll use the three Unforgivable curses from the Harry Potter series — Avada Kedavra, Crucio, and Imperio.

What Is the Strategy Pattern?

The Strategy Pattern is a design principle that allows you to switch between different algorithms or behaviors at runtime. It helps make your code flexible and adaptable by allowing you to plug in different strategies without changing the core logic of your application.

This approach is useful in scenarios where you have different implementations for a specific task of functionality and want to make your system more adaptable to changes. It promotes a more modular code structure by separating the algorithmic details from the main logic of your application.

Step 1: Implementing Strategy

Picture yourself as a dark wizard who strives to master the power of Unforgivable curses with Spring. Our mission is to implement all three curses — Avada Kedavra, Crucio and Imperio. After that, we will switch between curses (strategies) in runtime.

Let's start with our strategy interface:

Java
 
public interface CurseStrategy {  

    String useCurse();

	String curseName();
}


In the next step, we need to implement all Unforgivable curses:

Java
 
@Component  
public class CruciatusCurseStrategy implements CurseStrategy {  
  
	@Override  
	public String useCurse() {  
		return "Attack with Crucio!";  
	}  
  
	@Override  
	public String curseName() {  
		return "Crucio";  
	}  
}


@Component  
public class ImperiusCurseStrategy implements CurseStrategy {  
  
	@Override  
	public String useCurse() {  
		return "Attack with Imperio!";  
	}  
  
	@Override  
	public String curseName() {  
		return "Imperio";  
	}  
}

@Component  
public class KillingCurseStrategy implements CurseStrategy {  
  
	@Override  
	public String useCurse() {  
		return "Attack with Avada Kedavra!";  
	}  
  
	@Override  
	public String curseName() {  
		return "Avada Kedavra";  
	}  
}


Step 2: Inject Curses as List

Spring brings a touch of magic that allows us to inject multiple implementations of an interface as a List so we can use it to inject strategies and switch between them.

But let's first create the foundation: Wizard interface.

Java
 
public interface Wizard {  
	String castCurse(String name); 
}


And we can inject our curses (strategies) into the Wizard and filter the desired one.

Java
 
@Service  
public class DarkArtsWizard implements Wizard {  
  
	private final List<CurseStrategy> curses;  
  
	public DarkArtsListWizard(List<CurseStrategy> curses) {  
		this.curses = curses;  
	}  
  
	@Override  
	public String castCurse(String name) {  
		return curses.stream()  
			.filter(s -> name.equals(s.curseName()))  
			.findFirst()  
			.orElseThrow(UnsupportedCurseException::new)  
			.useCurse();  
	}  
}


 UnsupportedCurseException is also created if the requested curse does not exist.

Java
 
public class UnsupportedCurseException extends RuntimeException {  
}


And we can verify that curse casting is working:

Java
 
@SpringBootTest  
class DarkArtsWizardTest {  
  
	@Autowired  
	private DarkArtsWizard wizard;  
  
	@Test  
	public void castCurseCrucio() {  
		assertEquals("Attack with Crucio!", wizard.castCurse("Crucio"));  
	}  
  
	@Test  
	public void castCurseImperio() {  
		assertEquals("Attack with Imperio!", wizard.castCurse("Imperio"));  
	}  
  
	@Test  
	public void castCurseAvadaKedavra() {  
		assertEquals("Attack with Avada Kedavra!", wizard.castCurse("Avada Kedavra"));  
	}  
  
	@Test  
	public void castCurseExpelliarmus() {  
		assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Abrakadabra"));  
	}  
}


Another popular approach is to define the canUse method instead of curseName. This will return boolean  and allows us to use more complex filtering like:

Java
 
public interface CurseStrategy {  

    String useCurse();

	boolean canUse(String name, String wizardType);
}

@Component  
public class CruciatusCurseStrategy implements CurseStrategy {  
  
	@Override  
	public String useCurse() {  
		return "Attack with Crucio!";  
	}  
  
	@Override  
	public boolean canUse(String name, String wizardType) {  
		return "Crucio".equals(name) && "Dark".equals(wizardType);  
	}  
}

@Service  
public class DarkArtstWizard implements Wizard {  
  
	private final List<CurseStrategy> curses;  
  
	public DarkArtsListWizard(List<CurseStrategy> curses) {  
		this.curses = curses;  
	}  
  
	@Override  
	public String castCurse(String name) {  
		return curses.stream()  
			.filter(s -> s.canUse(name, "Dark")))  
			.findFirst()  
			.orElseThrow(UnsupportedCurseException::new)  
			.useCurse();  
	}  
}


Pros: Easy to implement. 

Cons: Runs through a loop every time, which can lead to slower execution times and increased processing overhead.

Step 3: Inject Strategies as Map

We can easily address the cons from the previous section. Spring lets us inject a Map with bean names and instances. It simplifies the code and improves its efficiency. 

Java
 
@Service  
public class DarkArtsWizard implements Wizard {  
  
	private final Map<String, CurseStrategy> curses;  
  
	public DarkArtsMapWizard(Map<String, CurseStrategy> curses) {  
		this.curses = curses;  
	}  
  
	@Override  
	public String castCurse(String name) {  
		CurseStrategy curse = curses.get(name);  
		if (curse == null) {  
			throw new UnsupportedCurseException();  
		}  
		return curse.useCurse();  
	}  
}


This approach has a downside: Spring injects the bean name as the key for the Map, so strategy names are the same as the bean names like cruciatusCurseStrategy. This dependency on Spring's internal bean names might cause problems if Spring's code or our class names change without notice.

Let's check that we're still capable of casting those curses:

Java
 
@SpringBootTest  
class DarkArtsWizardTest {  
  
	@Autowired  
	private DarkArtsWizard wizard;  
  
	@Test  
	public void castCurseCrucio() {  
		assertEquals("Attack with Crucio!", wizard.castCurse("cruciatusCurseStrategy"));  
	}  
  
	@Test  
	public void castCurseImperio() {  
		assertEquals("Attack with Imperio!", wizard.castCurse("imperiusCurseStrategy"));  
	}  
  
	@Test  
	public void castCurseAvadaKedavra() {  
		assertEquals("Attack with Avada Kedavra!", wizard.castCurse("killingCurseStrategy"));  
	}  
  
	@Test  
	public void castCurseExpelliarmus() {  
		assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Crucio"));  
	}  
}


Pros: No loops. 

Cons: Dependency on bean names, which makes the code less maintainable and more prone to errors if names are changed or refactored.

Step 4: Inject List and Convert to Map

Cons of Map injection can be easily eliminated if we inject List and convert it to Map:

Java
 
@Service  
public class DarkArtsWizard implements Wizard {  
  
	private final Map<String, CurseStrategy> curses;  
  
	public DarkArtsMapWizard(List<CurseStrategy> curses) {  
		this.curses = curses.stream()  
			.collect(Collectors.toMap(CurseStrategy::curseName, Function.identity()));
	}  
  
	@Override  
	public String castCurse(String name) {  
		CurseStrategy curse = curses.get(name);  
		if (curse == null) {  
			throw new UnsupportedCurseException();  
		}  
		return curse.useCurse();  
	}  
}


With this approach, we can move back to use curseName instead of Spring's bean names for Map keys (strategy names).

Step 5: @Autowire in Interface

Spring supports autowiring into methods. The simple example of autowiring into methods is through setter injection. This feature allows us to use @Autowired in a default method of an interface so we can register each CurseStrategy in the Wizard interface without needing to implement a registration method in every strategy implementation.

Let's update the Wizard interface by adding a registerCurse method:

Java
 
public interface Wizard {  
  
	String castCurse(String name);  
  
	void registerCurse(String curseName, CurseStrategy curse)
}


This is the Wizard implementation:

Java
 
@Service  
public class DarkArtsWizard implements Wizard {  
  
	private final Map<String, CurseStrategy> curses = new HashMap<>();  
  
	@Override  
	public String castCurse(String name) {  
		CurseStrategy curse = curses.get(name);  
		if (curse == null) {  
			throw new UnsupportedCurseException();  
		}  
		return curse.useCurse();  
	}  
  
	@Override  
	public void registerCurse(String curseName, CurseStrategy curse) {  
		curses.put(curseName, curse);  
	}  
}


Now, let's update the CurseStrategy interface by adding a method with the @Autowired annotation:

Java
 
public interface CurseStrategy {  
  
	String useCurse();  
  
	String curseName();  
  
	@Autowired  
	default void registerMe(Wizard wizard) {  
		wizard.registerCurse(curseName(), this);  
	}  
}


At the moment of injecting dependencies, we register our curse into the Wizard.

Pros: No loops, and no reliance on inner Spring bean names. 

Cons: No cons, pure dark magic.

Conclusion

In this article, we explored the Strategy pattern in the context of Spring. We assessed different strategy injection approaches and demonstrated an optimized solution using Spring's capabilities.

The full source code for this article can be found on GitHub.

Spring Framework Strategy pattern Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • IntelliJ and Java Spring Microservices: Productivity Tips With GitHub Copilot
  • Composing Custom Annotations in Spring

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: