In this lesson, you will learn how to use trait-based dispatch to swap internal logic algorithms at runtime without changing the code that consumes them.
Defining Swappable Strategies
The Strategy Pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. In Rust, we achieve this by defining a trait that represents the "strategy" and then injecting an implementation of that trait into a struct that needs to execute that logic.
By using trait objects (&dyn Trait), we can swap these strategies dynamically at runtime.
The Strategy in Action
Imagine a simple PaymentProcessor that can use different calculation logic. Instead of hardcoding the math, we define a trait:
trait DiscountStrategy {
fn apply_discount(&self, price: f64) -> f64;
}
struct NoDiscount;
impl DiscountStrategy for NoDiscount {
fn apply_discount(&self, price: f64) -> f64 { price }
}
struct SeasonalSale;
impl DiscountStrategy for SeasonalSale {
fn apply_discount(&self, price: f64) -> f64 { price * 0.8 }
}
Now, we create a context that holds the strategy. By holding a Box<dyn DiscountStrategy>, the context doesn't care which specific logic is used, only that it satisfies the interface.
struct Order {
price: f64,
strategy: Box<dyn DiscountStrategy>,
}
impl Order {
fn finalize(&self) {
let final_price = self.strategy.apply_discount(self.price);
println!("Final price: {}", final_price);
}
}
Why This Works
The Order struct acts as the dispatcher. Because we defined the logic in a trait, the Order struct is decoupled from the implementation details. If you need to add a "Black Friday" discount later, you simply create a new struct that implements DiscountStrategy and swap it in. The Order code remains untouched, demonstrating the power of modularity through polymorphism.
This approach is highly effective for writing testable, maintainable code because you can easily swap real logic for mocked versions during unit tests without modifying your core business structures.
Key Takeaway: The Strategy Pattern uses trait-based dispatch to decouple your application logic from specific implementations, allowing you to swap behaviors at runtime by passing trait objects into your structs.