Strategy design pattern in Rust
Page content
I just followed Rust Design Pattern - Strategy (aka Policy).
Simple explanation
Make code abstract as possible.
… given an algorithm solving a particular problem, we define only the skeleton of the algorithm at an abstract level, and we separate the specific algorithm’s implementation into different parts.
The stragety pattern are used frequently with dependency inversion (it is also related with high level modules).
Example
Code
use std::collections::HashMap;
type Data = HashMap<String, u32>;
trait Formatter {
fn format(&self, data: &Data, buf: &mut String);
}
struct Report;
impl Report {
fn generate<T: Formatter>(g: T, s: &mut String) {
let mut data = HashMap::new();
data.insert("one".to_string(), 1);
data.insert("two".to_string(), 2);
g.format(&data, s);
}
}
struct Text;
impl Formatter for Text {
fn format(&self, data: &Data, buf: &mut String) {
for (k, v) in data {
let entry = format!("{} {}\n", k, v);
buf.push_str(&entry);
}
}
}
struct Json;
impl Formatter for Json {
fn format(&self, data: &Data, buf: &mut String) {
buf.push('[');
for (k, v) in data.into_iter() {
let entry = format!(r#"{{"{}":"{}"}}"#, k, v);
buf.push_str(&entry);
buf.push(',');
}
buf.pop(); // remove extra , at the end
buf.push(']');
}
}
fn main() {
let mut s = String::from("");
Report::generate(Text, &mut s);
// s = "two 2\none 1\n"
assert!(s.contains("one 1"));
assert!(s.contains("two 2"));
s.clear(); // reuse the same buffer
Report::generate(Json, &mut s);
// s = [{"one":"1"},{"two":"2"}]
assert!(s.contains(r#"{"one":"1"}"#));
assert!(s.contains(r#"{"two":"2"}"#));
}
Quick explanation
- In
main()function, user useReport::generatefunction, and this is the only function user uses. - But the behavior of the function should be changed by the
strctof its first parameter. In this case,TextorJson. - [Abstraction] The simple solution would be to introduce a trait
Formatterwhich hasformatmethod. If you implement the trait for the structs,TextandJson, differently andgeneratefunction takes theFormatter-bounded struct, the only thinggeneratershould care about is “callingformatmethod of the first parameter struct”.- By virtue of trait, we don’t need to create if/else switch in
generatemethod.
- By virtue of trait, we don’t need to create if/else switch in
- From
generatefunction point, the actual type of its first parameter (TextorJson) is called stragety. generatefunction is an abstract function because it doesn’t know how strategies implementformat.
Actual use case
- Suppose that we need another format, like CSV. This is a new strategy.
- In that case, you need to create a new struct
CSVand implementformatfor this struct so thatgeneratecan use this stragety.