Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

Strategy — Behavioral Design Pattern

What is it?

The Strategy pattern defines a family of interchangeable algorithms (strategies), encapsulates each one in its own type, and lets the caller swap them at runtime without changing the code that uses them. The object that holds and delegates to a strategy is called the context.

It sits in the Behavioral family because it defines how objects collaborate to carry out behavior — the context delegates what to do; the strategy decides how to do it.


When to use it

  • You have multiple variants of an algorithm and want to switch between them at runtime.
  • You want to eliminate large if/else or switch blocks that select behavior based on a type or flag.
  • Different parts of the system need to compose or extend behavior without inheriting from a common base class.

Avoid it when you only ever have one algorithm and the extra indirection adds no value.


Go's approach vs. OOP languages

Classical OOP uses an abstract Strategy base class. Go replaces this with an interface — leaner and more composable:

Mechanism Purpose
PaymentMode interface Declares the strategy contract (Name, Settle)
Concrete mode structs (IMPSMode, UPIMode, NEFTMode) Each encapsulates one payment algorithm
PaymentExecutor (context) Holds a PaymentMode field and delegates execution to it
SetPaymentMode Swaps the strategy at runtime with no structural change

Structure of this implementation

strategy/
├── payment_transfer_strategy/
│   └── transfer_modes.go   # Interface, context, and all concrete strategies
└── main.go                  # Usage demo
type PaymentMode interface {
    Name() string
    Settle(id string, amount float64) error
}

Any type that implements Name and Settle is a valid payment strategy — no embedding, no registration required.

The context — PaymentExecutor

PaymentExecutor holds the active strategy in a strategy PaymentMode field. It exposes two methods:

Method Behavior
SetPaymentMode(mode) Replaces the current strategy
ExecuteSettlement(id, amount) Guards (nil strategy, non-positive amount), then delegates to strategy.Settle

The guard logic lives in the context, not in individual strategies — they only contain the algorithm itself.

Concrete strategies

Type Description
IMPSMode Immediate Payment Service — real-time interbank transfer
UPIMode Unified Payments Interface — mobile-first instant transfer
NEFTMode National Electronic Funds Transfer — batch-settled transfer

All three implement the same PaymentMode interface and are fully interchangeable from the context's perspective.


Runtime behavior

var executor PaymentExecutor

executor.SetPaymentMode(&NEFTMode{})
executor.ExecuteSettlement("aniket", 9999999)
// → [NEFT:Settle] Amount 9999999.000000 to aniket

executor.SetPaymentMode(&UPIMode{})
executor.ExecuteSettlement("aniket", 500)
// → [UPI:Settle] Amount 500.000000 to aniket

The executor's code never changes — only the injected strategy does.


Adding a new strategy

  1. Define a new struct (e.g., RTGSMode).
  2. Implement Name() string and Settle(id string, amount float64) error.
  3. Pass it to SetPaymentMode.

No existing code changes — the context, the interface, and the other strategies are untouched. This is the Open/Closed Principle in practice.


Key interview talking points

Q: How does Strategy differ from Chain of Responsibility? Strategy selects one algorithm and executes it. Chain of Responsibility passes a request through multiple handlers in sequence, each of which may or may not handle it. Strategy is about substitution; CoR is about delegation and short-circuiting.

Q: How does Strategy differ from a simple function argument? When the algorithm is a single function with no state, a function argument (first-class function) is simpler and idiomatic Go. Strategy makes sense when the algorithm needs its own state, configuration, or helper methods — the struct form gives you that.

Q: Where does the switching logic live? In the caller that calls SetPaymentMode. Strategy does not eliminate the selection decision — it moves it to a clean, explicit call site rather than burying it inside if/else branches.

Q: Is this the same as dependency injection? Structurally yes — injecting a PaymentMode into PaymentExecutor is DI. Strategy is the GoF pattern name for this specific form of DI applied to interchangeable algorithms.

Q: How would you make PaymentExecutor thread-safe? Protect the strategy field with a sync.RWMutex — a read lock in ExecuteSettlement, a write lock in SetPaymentMode.