Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › Classes and objects

Strategy pattern

What you have to change when one more payment method is added

Each payment method (card, bank transfer, Kakao Pay) is processed in a different way. One way to write this is to branch with if-else. Another is to keep a separate implementation for each payment method and swap the implementations in and out (the Strategy pattern).

Checked against real execution · Checked with code of the same structure on JDK 21

Concepts in this lab

Strategy pattern
A pattern that defines an algorithm or behavior that may change as an interface and implements each variant as a separate class, so that it can be swapped at run time. The code that uses it depends only on the interface, so adding a new variant does not require changing the existing code.
Design pattern
A proven solution to a problem that comes up repeatedly in software design, given a name and written down. These solutions serve as a shared vocabulary among developers, so a design intention can be communicated briefly. Each one is a means of solving a specific problem, so applying one where it is not needed only adds complexity.
Polymorphism
This is the property that calling the same method through a reference of the same type runs different behavior depending on the kind of the actual object. A variable of a supertype (an interface or a superclass) can hold objects of several subtypes, and which implementation runs is decided at run time (dynamic method lookup). It lets you add a new implementation without changing the calling code.
SOLID
These are five principles of object-oriented design. They are the Single Responsibility Principle (SRP: a class has only one reason to change), the Open-Closed Principle (OCP: extend without modifying existing code), the Liskov Substitution Principle (LSP: a subtype must be able to replace its supertype), the Interface Segregation Principle (ISP: keep interfaces small so that code does not depend on methods it does not use), and the Dependency Inversion Principle (DIP: depend on abstractions, not on concrete classes). The goal is a structure that holds up well under change.
Interface
This is a type that defines the list of methods (the contract) that a class must implement. It cannot have instance variables, and one class can implement several of them. If the calling code depends only on this type, it is easy to change the implementing class or to replace it with a fake implementation for testing.
Dependency injection (DI)
This is an approach in which an object does not create the other objects it uses (its dependencies) but receives them from outside. In Spring, the container supplies the dependencies through a constructor, a setter, or a field as it creates a bean. It becomes easier to change the implementing class or to put in a fake object in a test, and the coupling between classes becomes lower.

Try these

Running with the default settings

  1. The handling of each payment method is written as if-else branches inside the methods of PaymentService. The refund and cancel methods have branches of the same shape.
  2. Adding Naver Pay: You have to insert one else if into each of the three methods pay(), refund(), and cancel(). Because you are modifying methods that already worked, you have to test card and Kakao Pay again as well. If you miss even one place, only that feature throws an exception at run time.
  3. A payment by Kakao Pay is requested: pay("KAKAO", order).
  4. The code compares type with "CARD". They do not match. Execution moves on to the next condition.
  5. The code compares type with "BANK". They do not match. Execution moves on to the next condition.
  6. The code compares type with "KAKAO". They match. The Kakao Pay handling code inside this block runs.

Result of this run

Code

@Service
class PaymentService {
    void pay(String type, Order order) {
        if (type.equals("CARD")) {
            // request approval from the card company ... (30 lines)
        } else if (type.equals("BANK")) {
            // bank transfer ... (25 lines)
        } else if (type.equals("KAKAO")) {
            // Kakao Pay ... (40 lines)
        } else if (type.equals("NAVER")) {      // added: an existing method is modified
            // Naver Pay ... (35 lines)
        } else {
            throw new IllegalArgumentException(type);
        }
    }
    // the same branches are repeated in refund() and cancel()
}

Assumptions of this model

The actual payment processing is left out, and only the structures are compared. That the if-else version has the same branches in refund and cancel as well is an assumption based on a common situation. The definition of the Strategy pattern follows the description in the GoF book Design Patterns, and injecting all the implementations of the same interface at once as a List is a standard Spring feature. Two things were confirmed with code of the same structure on JDK 21 on this PC: that the method of the implementation object taken from the map is the one that actually runs, and the number of comparisons in the if-else version. The part where Spring injects the implementations was not run.

Previous lab: Immutability and defensive copies · Next lab: == and equals

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these