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::generate
function, and this is the only function user uses. - But the behavior of the function should be changed by the
strct
of its first parameter. In this case,Text
orJson
. - [Abstraction] The simple solution would be to introduce a trait
Formatter
which hasformat
method. If you implement the trait for the structs,Text
andJson
, differently andgenerate
function takes theFormatter
-bounded struct, the only thinggenerater
should care about is “callingformat
method of the first parameter struct”.- By virtue of trait, we don’t need to create if/else switch in
generate
method.
- By virtue of trait, we don’t need to create if/else switch in
- From
generate
function point, the actual type of its first parameter (Text
orJson
) is called stragety. generate
function 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
CSV
and implementformat
for this struct so thatgenerate
can use this stragety.