Fundamentals 28 min read

Mastering the Strategy Pattern in Java: Real‑World Pricing and Payment Examples

This article explains the Strategy pattern with Java, showing how to refactor a naïve pricing module into a flexible, open‑closed design, and then extends the concept to payment processing and thread‑pool rejection handling, complete with code samples and UML diagrams.

Programmer DD
Programmer DD
Programmer DD
Mastering the Strategy Pattern in Java: Real‑World Pricing and Payment Examples

Before introducing the Strategy pattern, the article presents a simple pricing module that uses a single quote method with multiple if‑else branches for new, old, and VIP customers, highlighting problems such as a bloated method and violation of the Open‑Closed Principle.

Improving the Design

The code is refactored so each discount algorithm is extracted into its own private method, reducing the size of quote but still requiring modifications to the method whenever a new customer type is added.

Strategy Pattern Definition

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, separating the algorithm from the client that uses it.

Structure

Strategy interface (e.g., IStrategy) that declares the algorithm method.

Concrete strategy classes that implement the interface.

Strategy context that holds a reference to a strategy and delegates calls.

General Strategy Interface

package strategy.examp01;
public interface IStrategy {
    void algorithmMethod();
}

Concrete Strategies

package strategy.examp01;
public class ConcreteStrategy implements IStrategy {
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
public class ConcreteStrategy2 implements IStrategy {
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

Strategy Context

package strategy.examp01;
public class StrategyContext {
    private IStrategy strategy;
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
    public void contextMethod() {
        strategy.algorithmMethod();
    }
}

Client Usage

package strategy.examp01;
public class Client {
    public static void main(String[] args) {
        IStrategy strategy = new ConcreteStrategy2();
        StrategyContext ctx = new StrategyContext(strategy);
        ctx.contextMethod();
    }
}

Applying the Pattern to Pricing

A common IQuoteStrategy interface is introduced, with implementations for new, old, VIP, and later MVP customers. The QuoteContext receives a concrete strategy and delegates the price calculation.

package strategy.examp02;
public interface IQuoteStrategy {
    BigDecimal getPrice(BigDecimal originalPrice);
}
package strategy.examp02;
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
}
package strategy.examp02;
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户享有9折优惠!");
        return originalPrice.multiply(new BigDecimal(0.9)).setScale(2, BigDecimal.ROUND_HALF_UP);
    }
}
package strategy.examp02;
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户享有8折优惠!");
        return originalPrice.multiply(new BigDecimal(0.8)).setScale(2, BigDecimal.ROUND_HALF_UP);
    }
}

Adding an MVP customer only requires a new strategy class; the existing code remains untouched, demonstrating compliance with the Open‑Closed Principle.

Deeper Understanding

The pattern’s power lies in extracting equal‑rank algorithms into independent classes, allowing dynamic replacement while keeping the client simple. Historical analogies (e.g., Liu Bei’s three battle plans) illustrate how interchangeable strategies are chosen by the client.

Fallback Example

A context tries an UpperStrategy; if it fails, it catches the exception and switches to MiddleStrategy, showing how a context can handle strategy selection at runtime.

public interface IOccupationStrategyWestOfSiChuan {
    void occupationWestOfSiChuan(String msg);
}
public class UpperStrategy implements IOccupationStrategyWestOfSiChuan {
    @Override
    public void occupationWestOfSiChuan(String msg) {
        if (msg == null || msg.length() < 5) {
            System.out.println("由于计划泄露,上上计策失败!");
            int i = 100/0; // trigger exception
        }
        System.out.println("挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!");
    }
}
public class OccupationContext {
    public void occupationWestOfSichuan(String msg) {
        IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy();
        try {
            strategy.occupationWestOfSiChuan(msg);
        } catch (Exception e) {
            strategy = new MiddleStrategy();
            strategy.occupationWestOfSiChuan(msg);
        }
    }
}

Payment Strategy Example

A PayStrategy interface abstracts payment methods. Implementations for RMB, Dollar, and Account payments demonstrate two ways of extending context: either by adding fields to the context or by letting the strategy carry its own data.

public interface PayStrategy {
    void pay(PayContext ctx);
}
public class RMBPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        System.out.println("现在给:" + ctx.getUsername() + " 人民币支付 " + ctx.getMoney() + "元!");
    }
}
public class PayContext {
    private String username;
    private double money;
    private PayStrategy payStrategy;
    public PayContext(String username, double money, PayStrategy payStrategy) {
        this.username = username;
        this.money = money;
        this.payStrategy = payStrategy;
    }
    public void pay() { payStrategy.pay(this); }
    public String getUsername() { return username; }
    public double getMoney() { return money; }
}

The client creates different strategies and contexts to pay employees in different currencies or to a bank account.

Strategy in JDK: RejectedExecutionHandler

The ThreadPoolExecutor constructor accepts a RejectedExecutionHandler strategy to decide what to do when the work queue is full. Four built‑in strategies are shown.

public static class AbortPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException();
    }
}
public static class DiscardPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // silently discard
    }
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

Advantages of the Strategy Pattern

Encapsulates interchangeable algorithms behind a common interface, improving modularity.

Eliminates long if‑else chains, making code easier to read and maintain.

Facilitates extension: adding a new strategy requires only a new class, no changes to existing code.

Disadvantages

Clients must be aware of all available strategies to choose the appropriate one.

Increases the number of classes, which can be cumbersome for many strategies.

Only suitable for flat algorithm structures; complex hierarchical algorithms may need other patterns.

Essence of the Pattern

Separate the algorithm from its usage, allowing the client to select or switch implementations without altering the core logic, thereby adhering to the Open‑Closed and Liskov Substitution principles.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsSoftware ArchitectureStrategy PatternOpen/Closed Principle
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.