Applying the Strategy Pattern in Java Backend Development: Best Practices and Iterations
The article demonstrates how to replace fragile if‑else shop‑ranking logic in a Java backend with the Strategy pattern, progressing from simple factories and enums to Spring‑auto‑wired and generic factories, highlighting improved extensibility, reduced coupling, Open‑Closed compliance, and the trade‑off of added abstraction complexity.
The article explores the Strategy pattern, a behavioral design pattern, and demonstrates how to apply it effectively in Java backend projects. It starts with a brief introduction to design patterns and their role in improving code reusability, maintainability, and robustness.
Use Case : The example focuses on calculating shop rankings for different shop types (Taobao, Tmall, Taote) in an e‑commerce platform. The initial naive implementation uses a series of if‑else statements, which tightly couples business logic and violates the Single Responsibility and Open‑Closed principles.
if (Objects.equals("淘宝", shopType)) {</code><code> // 淘宝店铺等级计算逻辑</code><code>} else if (Objects.equals("天猫", shopType)) {</code><code> // 天猫店铺等级计算逻辑</code><code>} else if (Objects.equals("淘特", shopType)) {</code><code> // 淘特店铺等级计算逻辑</code><code>} else {</code><code> // ...</code><code>}To decouple the client from the concrete algorithms, the Strategy pattern is introduced. A common ShopRankHandler interface is defined, and each shop type gets its own implementation.
public interface ShopRankHandler {</code><code> /**</code><code> * Calculate shop rank</code><code> */</code><code> String calculate();</code><code>}Concrete strategies:
public class TbShopRankHandleImpl implements ShopRankHandler {</code><code> @Override</code><code> public String calculate() {</code><code> // specific logic</code><code> return rank;</code><code> }</code><code>} public class TmShopRankHandleImpl implements ShopRankHandler { … }</code><pre><code>public class TtShopRankHandleImpl implements ShopRankHandler { … }</code><p>The client obtains the appropriate strategy via a simple factory:</p><pre><code>public String acquireShopRank(String shopType) {</code><code> ShopRankHandler handler = null;</code><code> if (Objects.equals("淘宝", shopType)) {</code><code> handler = new TbShopRankHandleImpl();</code><code> } else if (Objects.equals("天猫", shopType)) {</code><code> handler = new TmShopRankHandleImpl();</code><code> } else if (Objects.equals("淘特", shopType)) {</code><code> handler = new TtShopRankHandleImpl();</code><code> }</code><code> return handler != null ? handler.calculate() : "";</code><code>}Pros : Clear separation of concerns, easy to extend with new shop types.
Cons : The client still needs to modify the if‑else block when a new strategy is added, which still breaches the Open‑Closed principle.
To further decouple, an enumeration + simple factory is introduced. An enum ShopTypeEnum holds the type code and description, and a ShopRankHandlerFactory maps type codes to strategy instances.
public enum ShopTypeEnum {</code><code> TAOBAO("A", "淘宝"),</code><code> TMALL("B", "天猫"),</code><code> TAOTE("C", "淘特");</code><code> private final String type;</code><code> private final String desc;</code><code> // getters …</code><code>} @Component</code><code>public class ShopRankHandlerFactory {</code><code> private static final Map<String, ShopRankHandler> MAP = ImmutableMap.<String, ShopRankHandler>builder()</code><code> .put(ShopTypeEnum.TAOBAO.getType(), new TbShopRankHandleImpl())</code><code> .put(ShopTypeEnum.TMALL.getType(), new TmShopRankHandleImpl())</code><code> .put(ShopTypeEnum.TAOTE.getType(), new TtShopRankHandleImpl())</code><code> .build();</code><code> public ShopRankHandler getStrategy(String shopType) {</code><code> return MAP.get(shopType);</code><code> }</code><code>}This removes the if‑else from the client, but the factory still needs manual updates when new strategies appear.
To achieve full decoupling, the article leverages Spring’s ApplicationContextAware and InitializingBean to auto‑wire all ShopRankHandler beans:
@Component</code><code>public class ShopRankHandlerFactory implements InitializingBean, ApplicationContextAware {</code><code> private ApplicationContext ctx;</code><code> private Map<String, ShopRankHandler> MAP;</code><code> @Override</code><code> public void afterPropertiesSet() {</code><code> MAP = ctx.getBeansOfType(ShopRankHandler.class).values().stream()</code><code> .filter(h -> StringUtils.isNotEmpty(h.getType()))</code><code> .collect(Collectors.toMap(ShopRankHandler::getType, Function.identity()));</code><code> }</code><code> @Override</code><code> public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }</code><code> public ShopRankHandler getStrategy(String shopType) { return MAP.get(shopType); }</code><code>}This eliminates any factory‑side modifications when new strategies are added.
Finally, a generic strategy factory is presented to avoid duplicated factory code across different business domains. A generic interface GenericInterface<E> defines getType(), and a HandlerFactory<E, T extends GenericInterface<E>> can be instantiated with any strategy interface.
public class HandlerFactory<E, T extends GenericInterface<E>> implements InitializingBean, ApplicationContextAware {</code><code> private ApplicationContext ctx;</code><code> private Class<T> strategyInterface;</code><code> private Map<E, T> MAP;</code><code> public HandlerFactory(Class<T> strategyInterface) { this.strategyInterface = strategyInterface; }</code><code> @Override public void afterPropertiesSet() {</code><code> MAP = ctx.getBeansOfType(strategyInterface).values().stream()</code><code> .collect(Collectors.toMap(T::getType, Function.identity()));</code><code> }</code><code> public T getStrategy(E type) { return MAP.get(type); }</code><code>}Using this generic factory, new strategy factories can be created with a single bean definition, dramatically reducing boilerplate.
Overall Advantages : Improved extensibility, reduced coupling, adherence to Open‑Closed principle, and higher development efficiency.
Potential Drawbacks : Increased abstraction and complexity, which may affect readability for newcomers.
The article concludes that while design patterns provide powerful tools, they should be applied judiciously to balance flexibility and simplicity.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
