Strategy
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the algorithm to vary independently from the clients that use it. This pattern avoids conditional complexity and promotes code reusability by defining a consistent interface for various algorithms.
This is particularly useful when you have multiple ways to accomplish a task, and you want to be able to select the appropriate algorithm at runtime, or when you need to be able to switch between algorithms easily. It promotes loose coupling between the client and the algorithm’s implementation.
Usage
The Strategy pattern is common in scenarios where you need flexible algorithms. Some examples include:
- Payment Processing: Different payment methods (credit card, PayPal, bank transfer) can be implemented as separate strategies, allowing a shopping cart to support multiple payment options.
- Sorting Algorithms: A sorting class can accept different sorting strategies (bubble sort, quicksort, merge sort) to sort data in various ways.
- Compression Algorithms: A file archiver can use different compression algorithms (ZIP, GZIP, BZIP2) based on user preference or file type.
- Validation Rules: Applying different validation rules to input data, such as email format, password strength, or data type.
Examples
-
Java 8 Streams API: The
Comparatorinterface in Java 8’s Streams API exemplifies the Strategy pattern. You can define different comparison strategies (e.g., comparing by name, by age, by date) and pass them to thesorted()method of a stream. The stream processing logic remains the same, but the sorting behavior changes based on the chosen comparator. -
Spring Data JPA: Spring Data JPA allows customizing query derivation by providing different
JpaEntityMappingsor implementing your ownQuerydslPredicateExecutor. Each strategy determines how Spring Data JPA translates method names into database queries. This allows developers to tailor query creation without affecting core Spring Data functionalities. -
Log4j 2: Log4j 2 uses strategies for different aspects of logging. For example, the
Layoutinterface defines a strategy for formatting log messages, allowing you to choose between plain text, JSON, XML, or other formats. Similarly, differentFilterimplementations act as strategies to determine which log messages are processed.
Specimens
15 implementationsThe Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This example demonstrates the Strategy pattern by defining different shipping cost calculation strategies (Standard, Express, Overnight). A ShippingContext class uses a ShippingStrategy interface to determine the cost, promoting loose coupling and flexibility. The Dart implementation leverages abstract classes and interfaces (protocols) which are core tenets of Dart’s type system and allow for clear contract definition between the context and strategies, fitting Dart’s emphasis on type safety and code organization.
// Define the Strategy interface
abstract class ShippingStrategy {
double calculateCost(double weight, String destination);
}
// Concrete Strategies
class StandardShipping implements ShippingStrategy {
@override
double calculateCost(double weight, String destination) {
// Some complex logic for standard shipping
double baseCost = 5.0;
double distanceCost = 0.1 * double.parse(destination.length); //simplified distance
return baseCost + (weight * 2) + distanceCost;
}
}
class ExpressShipping implements ShippingStrategy {
@override
double calculateCost(double weight, String destination) {
// Some complex logic for express shipping
double baseCost = 10.0;
double distanceCost = 0.25 * double.parse(destination.length); //simplified distance
return baseCost + (weight * 3) + distanceCost;
}
}
class OvernightShipping implements ShippingStrategy {
@override
double calculateCost(double weight, String destination) {
// Some complex logic for overnight shipping
double baseCost = 20.0;
double distanceCost = 0.5 * double.parse(destination.length); //simplified distance
return baseCost + (weight * 5) + distanceCost;
}
}
// Context
class ShippingContext {
final ShippingStrategy strategy;
ShippingContext(this.strategy);
double calculateShippingCost(double weight, String destination) {
return strategy.calculateCost(weight, destination);
}
}
void main() {
final standardShipping = StandardShipping();
final expressShipping = ExpressShipping();
final overnightShipping = OvernightShipping();
final context1 = ShippingContext(standardShipping);
final cost1 = context1.calculateShippingCost(2.0, "New York");
print("Standard Shipping Cost: \$${cost1.toStringAsFixed(2)}");
final context2 = ShippingContext(expressShipping);
final cost2 = context2.calculateShippingCost(2.0, "Los Angeles");
print("Express Shipping Cost: \$${cost2.toStringAsFixed(2)}");
final context3 = ShippingContext(overnightShipping);
final cost3 = context3.calculateShippingCost(2.0, "Miami");
print("Overnight Shipping Cost: \$${cost3.toStringAsFixed(2)}");
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This example demonstrates using traits in Scala to define different shipping cost calculation strategies. The ShippingCostCalculator class takes a ShippingCostStrategy as a constructor parameter, allowing the client to choose the desired algorithm at runtime without modifying the ShippingCostCalculator itself. This is idiomatic Scala because traits promote composition and functional programming principles, gracefully handling algorithm variations.
trait ShippingCostStrategy {
def calculate(packageWeight: Double, distance: Double): Double
}
class StandardShipping(val rate: Double) extends ShippingCostStrategy {
override def calculate(packageWeight: Double, distance: Double): Double =
packageWeight * rate * distance
}
class ExpeditedShipping(val rate: Double) extends ShippingCostStrategy {
override def calculate(packageWeight: Double, distance: Double): Double =
(packageWeight * rate * distance) * 1.5
}
class ShippingCostCalculator(strategy: ShippingCostStrategy) {
def calculateCost(packageWeight: Double, distance: Double): Double =
strategy.calculate(packageWeight, distance)
}
object StrategyExample {
def main(args: Array[String]): Unit = {
val standardShipping = new StandardShipping(0.1)
val expeditedShipping = new ExpeditedShipping(0.2)
val calculator1 = new ShippingCostCalculator(standardShipping)
val calculator2 = new ShippingCostCalculator(expeditedShipping)
println(s"Standard Shipping Cost: ${calculator1.calculateCost(5.0, 100.0)}")
println(s"Expedited Shipping Cost: ${calculator2.calculateCost(5.0, 100.0)}")
}
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This implementation uses an interface PaymentStrategy to define a common method for making payments. Concrete strategies like CreditCardPayment and PayPalPayment implement this interface with specific payment logic. A ShoppingCart class accepts a PaymentStrategy via dependency injection and uses it to process the payment, without needing to know the specifics of how the payment is made. This fits PHP’s flexible type system and encourages loose coupling through interfaces.
<?php
/**
* Payment Strategy Interface
*/
interface PaymentStrategy
{
public function pay(float $amount): bool;
}
/**
* Concrete Strategy: Credit Card Payment
*/
class CreditCardPayment implements PaymentStrategy
{
private string $cardNumber;
private string $cvv;
public function __construct(string $cardNumber, string $cvv)
{
$this->cardNumber = $cardNumber;
$this->cvv = $cvv;
}
public function pay(float $amount): bool
{
// Simulate credit card processing
echo "Paid $amount using Credit Card ($this->cardNumber)\n";
return true;
}
}
/**
* Concrete Strategy: PayPal Payment
*/
class PayPalPayment implements PaymentStrategy
{
private string $email;
public function __construct(string $email)
{
$this->email = $email;
}
public function pay(float $amount): bool
{
// Simulate PayPal processing
echo "Paid $amount using PayPal ($this->email)\n";
return true;
}
}
/**
* Context: Shopping Cart
*/
class ShoppingCart
{
private PaymentStrategy $paymentStrategy;
public function __construct(PaymentStrategy $paymentStrategy)
{
$this->paymentStrategy = $paymentStrategy;
}
public function setPaymentStrategy(PaymentStrategy $paymentStrategy): void
{
$this->paymentStrategy = $paymentStrategy;
}
public function checkout(float $amount): bool
{
return $this->paymentStrategy->pay($amount);
}
}
// Usage
$cart = new ShoppingCart(new CreditCardPayment("1234-5678-9012-3456", "123"));
$cart->checkout(100.00);
$cart->setPaymentStrategy(new PayPalPayment("user@example.com"));
$cart->checkout(50.00);
?>
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. Here, we define different shipping cost calculation strategies (e.g., for ground, air). The ShippingContext class accepts a strategy object and delegates the cost calculation to it. This avoids hardcoding shipping logic within the ShippingContext and makes it easy to add new shipping methods without modification. It utilizes Ruby’s duck typing and block passing to achieve a flexible strategy implementation that aligns with the language’s dynamic nature.
# Strategy interface (can be implicit in Ruby due to duck typing)
module ShippingStrategy
def calculate_cost(package)
raise NotImplementedError, "Subclasses must implement calculate_cost"
end
end
# Concrete strategies
class GroundShipping
include ShippingStrategy
def calculate_cost(package)
package.weight * 0.5
end
end
class AirShipping
include ShippingStrategy
def calculate_cost(package)
package.weight * 1.5 + 10 # Add a base fee for air shipping
end
end
class FreeShipping
include ShippingStrategy
def calculate_cost(package)
0
end
end
# Context
class ShippingContext
attr_reader :shipping_strategy
def initialize(shipping_strategy)
@shipping_strategy = shipping_strategy
end
def set_strategy(strategy)
@shipping_strategy = strategy
end
def calculate_shipping_cost(package)
shipping_strategy.calculate_cost(package)
end
end
# Simple Package class for demonstration
class Package
attr_reader :weight
def initialize(weight)
@weight = weight
end
end
# Example usage
package = Package.new(10)
ground_shipping = GroundShipping.new
shipping = ShippingContext.new(ground_shipping)
puts "Ground Shipping Cost: #{shipping.calculate_shipping_cost(package)}"
air_shipping = AirShipping.new
shipping.set_strategy(air_shipping)
puts "Air Shipping Cost: #{shipping.calculate_shipping_cost(package)}"
free_shipping = FreeShipping.new
shipping.set_strategy(free_shipping)
puts "Free Shipping Cost: #{shipping.calculate_shipping_cost(package)}"
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. In this Swift implementation, we define a protocol SortingStrategy with a single method sort. Different concrete strategies like BubbleSortStrategy and QuickSortStrategy conform to this protocol, each implementing a distinct sorting algorithm. A Sorter class uses a SortingStrategy instance to perform the sorting, delegating the actual sorting logic. This is idiomatic Swift due to its strong use of protocols for defining behavior and dependency injection for achieving flexibility.
// Define the Strategy interface (Protocol)
protocol SortingStrategy {
func sort(list: [Int]) -> [Int]
}
// Concrete Strategies
struct BubbleSortStrategy: SortingStrategy {
func sort(list: [Int]) -> [Int] {
var sortedList = list
let n = sortedList.count
for i in 0..<n {
for j in 0..<n-i-1 {
if sortedList[j] > sortedList[j+1] {
sortedList.swapAt(j, j+1)
}
}
}
return sortedList
}
}
struct QuickSortStrategy: SortingStrategy {
func sort(list: [Int]) -> [Int] {
guard list.count > 1 else { return list }
let pivot = list[0]
let less = list.filter { $0 < pivot }
let equal = list.filter { $0 == pivot }
let greater = list.filter { $0 > pivot }
return QuickSortStrategy().sort(list: less) + equal + QuickSortStrategy().sort(list: greater)
}
}
// Context
class Sorter {
private var strategy: SortingStrategy
init(strategy: SortingStrategy) {
self.strategy = strategy
}
func setStrategy(strategy: SortingStrategy) {
self.strategy = strategy
}
func sortList(list: [Int]) -> [Int] {
return strategy.sort(list: list)
}
}
// Usage
let myList = [5, 1, 4, 2, 8]
// Use Bubble Sort
var bubbleSorter = Sorter(strategy: BubbleSortStrategy())
let sortedBubble = bubbleSorter.sortList(list: myList)
print("Bubble Sort: \(sortedBubble)")
// Switch to Quick Sort
bubbleSorter.setStrategy(strategy: QuickSortStrategy())
let sortedQuick = bubbleSorter.sortList(list: myList)
print("Quick Sort: \(sortedQuick)")
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from clients that use it. In this Kotlin example, we use a functional approach with function types to define different shipping strategies (e.g., ground, air). The ShippingService class accepts a strategy function as a dependency, allowing it to calculate shipping costs based on the chosen strategy without knowing the strategy’s implementation details. This leverages Kotlin’s first-class function support for a concise and flexible implementation, common in Kotlin’s style favoring immutability and functional composition.
// Strategy Interface (Function Type)
typealias ShippingStrategy = (weight: Double, distance: Double) -> Double
// Concrete Strategies
val groundShipping: ShippingStrategy = { weight, distance -> weight * 0.1 + distance * 0.05 }
val airShipping: ShippingStrategy = { weight, distance -> weight * 0.5 + distance * 0.2 }
val seaShipping: ShippingStrategy = { weight, distance -> weight * 0.02 + distance * 0.01 }
// Context
class ShippingService(private val strategy: ShippingStrategy) {
fun calculateShippingCost(weight: Double, distance: Double): Double {
return strategy(weight, distance)
}
}
// Usage
fun main() {
val groundService = ShippingService(groundShipping)
val airService = ShippingService(airShipping)
val seaService = ShippingService(seaShipping)
val weight = 10.0
val distance = 100.0
println("Ground Shipping Cost: ${groundService.calculateShippingCost(weight, distance)}")
println("Air Shipping Cost: ${airService.calculateShippingCost(weight, distance)}")
println("Sea Shipping Cost: ${seaService.calculateShippingCost(weight, distance)}")
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. Here, we define a trait CompressionStrategy representing the compression algorithms. Concrete strategies like ZipCompression and RarCompression implement this trait. A Compressor struct then utilizes a chosen CompressionStrategy to perform compression. This decouples the compression logic from the compressor itself. The implementation is idiomatic Rust as it relies on traits for abstraction and composition over inheritance, promoting flexibility and testability. Using Box<dyn Trait> allows us to hold different strategy implementations at runtime.
// Define the strategy trait
trait CompressionStrategy {
fn compress(&self, data: &str) -> String;
}
// Concrete strategy: Zip compression
struct ZipCompression;
impl ZipCompression {
fn new() -> Self {
ZipCompression
}
}
impl CompressionStrategy for ZipCompression {
fn compress(&self, data: &str) -> String {
format!("Zip compressed: {}", data)
}
}
// Concrete strategy: Rar compression
struct RarCompression;
impl RarCompression {
fn new() -> Self {
RarCompression
}
}
impl CompressionStrategy for RarCompression {
fn compress(&self, data: &str) -> String {
format!("Rar compressed: {}", data)
}
}
// Context: Compressor
struct Compressor {
strategy: Box<dyn CompressionStrategy>,
}
impl Compressor {
fn new(strategy: Box<dyn CompressionStrategy>) -> Self {
Compressor { strategy }
}
fn compress(&self, data: &str) -> String {
self.strategy.compress(data)
}
}
fn main() {
// Use different strategies
let zip_strategy = Box::new(ZipCompression::new());
let rar_strategy = Box::new(RarCompression::new());
let compressor_zip = Compressor::new(zip_strategy);
let compressor_rar = Compressor::new(rar_strategy);
let data = "This is some data to compress.";
println!("{}", compressor_zip.compress(data));
println!("{}", compressor_rar.compress(data));
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets you vary an algorithm independently from clients that use it. This implementation uses interfaces to define the strategy (a ShippingMethod interface with a CalculateCost method). Concrete strategies (like StandardShipping, ExpressShipping) provide different implementations of the cost calculation. A ShoppingCart struct then holds a ShippingMethod allowing the shipping cost calculation to be swapped at runtime without modifying the ShoppingCart itself. This leverages Go’s interface capabilities for flexible composition over inheritance.
package main
import "fmt"
// ShippingMethod is the interface for different shipping cost calculations.
type ShippingMethod interface {
CalculateCost(distance int) float64
}
// StandardShipping calculates the shipping cost based on a standard rate.
type StandardShipping struct {
ratePerMile float64
}
func (s *StandardShipping) CalculateCost(distance int) float64 {
return float64(distance) * s.ratePerMile
}
// ExpressShipping calculates the shipping cost based on an express rate.
type ExpressShipping struct {
ratePerMile float64
}
func (e *ExpressShipping) CalculateCost(distance int) float64 {
return float64(distance) * e.ratePerMile * 2 // Express is twice as expensive
}
// ShoppingCart represents a shopping cart with a selected shipping method.
type ShoppingCart struct {
shippingMethod ShippingMethod
}
// SetShippingMethod allows changing the shipping method at runtime.
func (c *ShoppingCart) SetShippingMethod(method ShippingMethod) {
c.shippingMethod = method
}
// CalculateTotalCost calculates the total shipping cost.
func (c *ShoppingCart) CalculateTotalCost(distance int) float64 {
return c.shippingMethod.CalculateCost(distance)
}
func main() {
standard := &StandardShipping{ratePerMile: 0.5}
express := &ExpressShipping{ratePerMile: 0.75}
cart := &ShoppingCart{}
cart.SetShippingMethod(standard)
fmt.Println("Standard Shipping Cost (10 miles):", cart.CalculateTotalCost(10))
cart.SetShippingMethod(express)
fmt.Println("Express Shipping Cost (10 miles):", cart.CalculateTotalCost(10))
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows selecting an algorithm at runtime without modifying the client code. This implementation achieves this by defining a function pointer type Operation representing the strategy. Different operation functions (e.g., addition, subtraction) conform to this type. A Calculator struct holds a pointer to the current Operation strategy and a data context. Changing the strategy is done by assigning a different function to the operation member of the Calculator. This is idiomatic C as it leverages function pointers for flexibility and avoids complex object hierarchies, keeping the code lightweight and performant.
#include <stdio.h>
// Define the strategy interface (function pointer type)
typedef int (*Operation)(int a, int b);
// Concrete strategies
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
// Context
typedef struct {
Operation operation;
int data; // Example data context
} Calculator;
// Function to set the operation (strategy)
void setOperation(Calculator *calculator, Operation op) {
calculator->operation = op;
}
// Function to perform the calculation using the current strategy
int calculate(Calculator *calculator, int b) {
return calculator->operation(calculator->data, b);
}
int main() {
Calculator calc;
calc.data = 10;
// Set the strategy to addition
setOperation(&calc, add);
printf("10 + 5 = %d\n", calculate(&calc, 5));
// Change the strategy to subtraction
setOperation(&calc, subtract);
printf("10 - 5 = %d\n", calculate(&calc, 5));
//Change the strategy to multiply.
setOperation(&calc, multiply);
printf("10 * 5 = %d\n", calculate(&calc, 5));
return 0;
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This is achieved by having a context class that holds a strategy object and delegates work to it.
The C++ code implements this using an abstract AlgorithmStrategy base class, defining a pure virtual execute method. Concrete strategies like SortAscending and SortDescending inherit from this and provide their own implementations. The DataProcessor class (the context) accepts an AlgorithmStrategy through its constructor and uses it to process data. Using polymorphism is idiomatic C++ for implementing this, allowing the DataProcessor to work with any concrete strategy without knowing its specific type, promoting loose coupling and extensibility.
#include <iostream>
#include <vector>
#include <algorithm>
// Abstract Strategy
class AlgorithmStrategy {
public:
virtual void execute(std::vector<int>& data) = 0;
virtual ~AlgorithmStrategy() = default;
};
// Concrete Strategies
class SortAscending : public AlgorithmStrategy {
public:
void execute(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
}
};
class SortDescending : public AlgorithmStrategy {
public:
void execute(std::vector<int>& data) override {
std::sort(data.begin(), data.end(), std::greater<int>());
}
};
// Context
class DataProcessor {
private:
AlgorithmStrategy* strategy;
public:
DataProcessor(AlgorithmStrategy* strategy) : strategy(strategy) {}
void processData(std::vector<int>& data) {
if (strategy) {
strategy->execute(data);
}
}
void setStrategy(AlgorithmStrategy* newStrategy) {
delete strategy;
strategy = newStrategy;
}
};
int main() {
std::vector<int> data = {5, 2, 8, 1, 9};
DataProcessor processor(new SortAscending());
processor.processData(data);
std::cout << "Ascending: ";
for (int x : data) {
std::cout << x << " ";
}
std::cout << std::endl;
processor.setStrategy(new SortDescending());
processor.processData(data);
std::cout << "Descending: ";
for (int x : data) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. In this C# example, we have a Context (OrderProcessor) that needs to apply a shipping cost calculation. Instead of hardcoding the logic, it relies on a ShippingCostStrategy interface. Concrete strategies like StandardShipping, ExpressShipping, and FreeShipping implement specific calculation formulas. The OrderProcessor’s constructor accepts the desired shipping strategy, enabling runtime algorithm selection. This follows C#’s principle of programming to interfaces and leveraging dependency injection for flexibility.
// Strategy Interface
public interface IShippingCostStrategy
{
decimal CalculateCost(decimal orderTotal, string destination);
}
// Concrete Strategies
public class StandardShipping : IShippingCostStrategy
{
public decimal CalculateCost(decimal orderTotal, string destination)
{
// Base cost + distance-based cost
return 5.00m + (orderTotal * 0.02m);
}
}
public class ExpressShipping : IShippingCostStrategy
{
public decimal CalculateCost(decimal orderTotal, string destination)
{
// Higher base cost + quicker delivery surcharge
return 15.00m + (orderTotal * 0.05m);
}
}
public class FreeShipping : IShippingCostStrategy
{
public decimal CalculateCost(decimal orderTotal, string destination)
{
return 0.00m;
}
}
// Context
public class OrderProcessor
{
private readonly IShippingCostStrategy _shippingCostStrategy;
public OrderProcessor(IShippingCostStrategy shippingCostStrategy)
{
_shippingCostStrategy = shippingCostStrategy;
}
public decimal CalculateShipping(decimal orderTotal, string destination)
{
return _shippingCostStrategy.CalculateCost(orderTotal, destination);
}
}
// Usage
public class Program
{
public static void Main(string[] args)
{
var standardShipping = new StandardShipping();
var expressShipping = new ExpressShipping();
var freeShipping = new FreeShipping();
var orderProcessor1 = new OrderProcessor(standardShipping);
var orderProcessor2 = new OrderProcessor(expressShipping);
var orderProcessor3 = new OrderProcessor(freeShipping);
decimal cost1 = orderProcessor1.CalculateShipping(100, "USA");
decimal cost2 = orderProcessor2.CalculateShipping(100, "USA");
decimal cost3 = orderProcessor3.CalculateShipping(100, "USA");
Console.WriteLine($"Standard Shipping Cost: {cost1}");
Console.WriteLine($"Express Shipping Cost: {cost2}");
Console.WriteLine($"Free Shipping Cost: {cost3}");
}
}
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This implementation uses TypeScript interfaces and classes to define the strategy and context. Different shipping cost calculation strategies (e.g., flat rate, weight-based) are implemented as separate classes adhering to a common ShippingCostStrategy interface. The ShoppingCart class (the context) accepts a strategy via dependency injection and uses it to calculate the final shipping cost. This approach is idiomatic TypeScript as it leverages strong typing with interfaces and classes for maintainability and scalability.
// Shipping Cost Strategy Interface
interface ShippingCostStrategy {
calculate(cartValue: number, weight: number): number;
}
// Concrete Strategy: Flat Rate Shipping
class FlatRateShippingStrategy implements ShippingCostStrategy {
private readonly rate: number;
constructor(rate: number = 10) {
this.rate = rate;
}
calculate(cartValue: number, weight: number): number {
return this.rate;
}
}
// Concrete Strategy: Weight Based Shipping
class WeightBasedShippingStrategy implements ShippingCostStrategy {
private readonly costPerKg: number;
constructor(costPerKg: number = 2.5) {
this.costPerKg = costPerKg;
}
calculate(cartValue: number, weight: number): number {
return weight * this.costPerKg;
}
}
// Context: Shopping Cart
class ShoppingCart {
private shippingStrategy: ShippingCostStrategy;
constructor(shippingStrategy: ShippingCostStrategy) {
this.shippingStrategy = shippingStrategy;
}
setShippingStrategy(strategy: ShippingCostStrategy): void {
this.shippingStrategy = strategy;
}
calculateShippingCost(cartValue: number, weight: number): number {
return this.shippingStrategy.calculate(cartValue, weight);
}
}
// Example Usage
const flatRateStrategy = new FlatRateShippingStrategy();
const weightBasedStrategy = new WeightBasedShippingStrategy(3);
const cart = new ShoppingCart(flatRateStrategy);
console.log("Flat Rate Shipping:", cart.calculateShippingCost(50, 5)); // Output: 10
cart.setShippingStrategy(weightBasedStrategy);
console.log("Weight Based Shipping:", cart.calculateShippingCost(50, 5)); // Output: 15
The Strategy pattern defines a family of algorithms, encapsulates each one as an object, and makes them interchangeable. This allows you to vary the algorithm used at runtime without modifying the client code. In this JavaScript example, we have a ShippingCostCalculator that accepts a strategy object with a calculate method. Different shipping strategies (Standard, Express, Overnight) implement the calculate method to determine the cost based on package weight and other factors. This leverages JavaScript’s first-class function capabilities; the strategy is essentially passed as a function, making the code flexible and maintainable, and avoiding conditional statements to switch between algorithms.
// Strategy Interface (can be implicit in JavaScript via duck typing)
// No need for a formal Interface
// Concrete Strategies
const standardShipping = {
calculate: (packageWeight, distance) => {
return packageWeight * 0.5 + distance * 0.1;
},
name: "Standard"
};
const expressShipping = {
calculate: (packageWeight, distance) => {
return packageWeight * 1.0 + distance * 0.25;
},
name: "Express"
};
const overnightShipping = {
calculate: (packageWeight, distance) => {
return packageWeight * 2.0 + distance * 0.5;
},
name: "Overnight"
};
// Context
class ShippingCostCalculator {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculateCost(packageWeight, distance) {
return this.strategy.calculate(packageWeight, distance);
}
}
// Example Usage
const calculator = new ShippingCostCalculator(standardShipping);
console.log(`Shipping cost with ${calculator.strategy.name} shipping: $${calculator.calculateCost(5, 100)}`);
calculator.setStrategy(expressShipping);
console.log(`Shipping cost with ${calculator.strategy.name} shipping: $${calculator.calculateCost(5, 100)}`);
calculator.setStrategy(overnightShipping);
console.log(`Shipping cost with ${calculator.strategy.name} shipping: $${calculator.calculateCost(5, 100)}`);
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This implementation utilizes Python’s first-class functions to represent different strategies for calculating shipping costs. A ShippingCostCalculator class takes a strategy function as a dependency, allowing different cost calculations (e.g., by weight, by volume, flat rate) to be used at runtime without modifying the calculator class itself. This approach is highly Pythonic due to its emphasis on functions as objects and the ease of passing them around, facilitating dynamic behavior.
from enum import Enum
class ShippingMethod(Enum):
WEIGHT = 1
VOLUME = 2
FLAT_RATE = 3
def calculate_by_weight(package_weight, rate_per_kg):
"""Calculates shipping cost based on weight."""
return package_weight * rate_per_kg
def calculate_by_volume(package_volume, rate_per_cubic_cm):
"""Calculates shipping cost based on volume."""
return package_volume * rate_per_cubic_cm
def calculate_flat_rate(shipping_fee):
"""Calculates a flat shipping rate."""
return shipping_fee
class ShippingCostCalculator:
"""
Calculates shipping costs using a given strategy.
"""
def __init__(self, strategy):
self.strategy = strategy
def calculate_cost(self, *args):
"""
Calculates the shipping cost using the currently selected strategy.
"""
return self.strategy(*args)
if __name__ == '__main__':
package_weight = 2.5
package_volume = 1000
flat_shipping_fee = 10
# Calculate cost by weight
weight_calculator = ShippingCostCalculator(calculate_by_weight)
weight_cost = weight_calculator.calculate_cost(package_weight, 5)
print(f"Shipping cost by weight: ${weight_cost}")
# Calculate cost by volume
volume_calculator = ShippingCostCalculator(calculate_by_volume)
volume_cost = volume_calculator.calculate_cost(package_volume, 0.01)
print(f"Shipping cost by volume: ${volume_cost}")
# Calculate flat rate
flat_rate_calculator = ShippingCostCalculator(calculate_flat_rate)
flat_cost = flat_rate_calculator.calculate_cost(flat_shipping_fee)
print(f"Flat rate shipping cost: ${flat_cost}")
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows an algorithm to vary independently from the clients that use it. This implementation uses a functional interface SortingStrategy to represent different sorting algorithms. The Sorter class accepts a SortingStrategy object in its constructor and delegates the sorting task to it. This promotes loose coupling and allows easily adding new sorting strategies without modifying the Sorter class itself. Java’s lambda expressions make defining the strategies concise and readable, aligning with modern Java practices for functional programming where applicable.
// SortingStrategy.java
@FunctionalInterface
interface SortingStrategy {
void sort(int[] array);
}
// Sorter.java
class Sorter {
private final SortingStrategy strategy;
public Sorter(SortingStrategy strategy) {
this.strategy = strategy;
}
public void performSort(int[] array) {
strategy.sort(array);
}
}
// Main.java
public class Main {
public static void main(String[] args) {
int[] data = {5, 2, 8, 1, 9, 4};
// Bubble Sort Strategy
Sorter bubbleSorter = new Sorter(array -> {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j] > array[j + 1]) {
// Swap
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
});
bubbleSorter.performSort(data.clone()); // Clone to avoid modifying original array
System.out.println("Bubble Sorted Array: " + java.util.Arrays.toString(data.clone()));
// Java's built-in Arrays.sort() Strategy
Sorter javaSorter = new Sorter(array -> java.util.Arrays.sort(array));
javaSorter.performSort(data.clone());
System.out.println("Java Sorted Array: " + java.util.Arrays.toString(data.clone()));
}
}