diff --git a/.gitignore b/.gitignore index 851a43b..29b71da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Generated output of mdbook /book +.DS_Store diff --git a/404.html b/404.html index a6aac1f..7d50856 100644 --- a/404.html +++ b/404.html @@ -97,7 +97,7 @@ diff --git a/additional_resources/design-principles.html b/additional_resources/design-principles.html index fd473a4..30744ef 100644 --- a/additional_resources/design-principles.html +++ b/additional_resources/design-principles.html @@ -95,7 +95,7 @@ diff --git a/additional_resources/index.html b/additional_resources/index.html index 51c835a..8fae933 100644 --- a/additional_resources/index.html +++ b/additional_resources/index.html @@ -95,7 +95,7 @@ diff --git a/anti_patterns/deny-warnings.html b/anti_patterns/deny-warnings.html index 7fd75a0..814bce1 100644 --- a/anti_patterns/deny-warnings.html +++ b/anti_patterns/deny-warnings.html @@ -95,7 +95,7 @@ diff --git a/anti_patterns/deref.html b/anti_patterns/deref.html index 8b19ced..8513a05 100644 --- a/anti_patterns/deref.html +++ b/anti_patterns/deref.html @@ -95,7 +95,7 @@ diff --git a/anti_patterns/index.html b/anti_patterns/index.html index e191536..c6e564b 100644 --- a/anti_patterns/index.html +++ b/anti_patterns/index.html @@ -95,7 +95,7 @@ diff --git a/functional/index.html b/functional/index.html index d714913..8235c01 100644 --- a/functional/index.html +++ b/functional/index.html @@ -95,7 +95,7 @@ diff --git a/idioms/coercion-arguments.html b/idioms/coercion-arguments.html index 8a5ce0b..383cac4 100644 --- a/idioms/coercion-arguments.html +++ b/idioms/coercion-arguments.html @@ -95,7 +95,7 @@ diff --git a/idioms/concat-format.html b/idioms/concat-format.html index a1b16bd..e39da8c 100644 --- a/idioms/concat-format.html +++ b/idioms/concat-format.html @@ -95,7 +95,7 @@ diff --git a/idioms/ctor.html b/idioms/ctor.html index 4f6b17e..cbc2542 100644 --- a/idioms/ctor.html +++ b/idioms/ctor.html @@ -95,7 +95,7 @@ diff --git a/idioms/default.html b/idioms/default.html index 7c598a7..94db75d 100644 --- a/idioms/default.html +++ b/idioms/default.html @@ -95,7 +95,7 @@ diff --git a/idioms/deref.html b/idioms/deref.html index a30be6b..f1b3333 100644 --- a/idioms/deref.html +++ b/idioms/deref.html @@ -95,7 +95,7 @@ diff --git a/idioms/dtor-finally.html b/idioms/dtor-finally.html index 7ae2e67..502dab8 100644 --- a/idioms/dtor-finally.html +++ b/idioms/dtor-finally.html @@ -95,7 +95,7 @@ diff --git a/idioms/ffi-accepting-strings.html b/idioms/ffi-accepting-strings.html index 4f0c1a5..3576084 100644 --- a/idioms/ffi-accepting-strings.html +++ b/idioms/ffi-accepting-strings.html @@ -95,7 +95,7 @@ diff --git a/idioms/ffi-errors.html b/idioms/ffi-errors.html index 78b7747..a28aac5 100644 --- a/idioms/ffi-errors.html +++ b/idioms/ffi-errors.html @@ -95,7 +95,7 @@ diff --git a/idioms/ffi-intro.html b/idioms/ffi-intro.html index ff08cdf..7edf828 100644 --- a/idioms/ffi-intro.html +++ b/idioms/ffi-intro.html @@ -95,7 +95,7 @@ diff --git a/idioms/ffi-passing-strings.html b/idioms/ffi-passing-strings.html index 08775e6..d133c81 100644 --- a/idioms/ffi-passing-strings.html +++ b/idioms/ffi-passing-strings.html @@ -95,7 +95,7 @@ diff --git a/idioms/index.html b/idioms/index.html index c68f774..3cbe9c8 100644 --- a/idioms/index.html +++ b/idioms/index.html @@ -95,7 +95,7 @@ diff --git a/idioms/mem-replace.html b/idioms/mem-replace.html index a040a06..6acad3b 100644 --- a/idioms/mem-replace.html +++ b/idioms/mem-replace.html @@ -95,7 +95,7 @@ diff --git a/idioms/on-stack-dyn-dispatch.html b/idioms/on-stack-dyn-dispatch.html index 76fd94d..4f651e2 100644 --- a/idioms/on-stack-dyn-dispatch.html +++ b/idioms/on-stack-dyn-dispatch.html @@ -95,7 +95,7 @@ diff --git a/idioms/option-iter.html b/idioms/option-iter.html index 9a2be71..33f3992 100644 --- a/idioms/option-iter.html +++ b/idioms/option-iter.html @@ -95,7 +95,7 @@ diff --git a/idioms/pass-var-to-closure.html b/idioms/pass-var-to-closure.html index 7c8ab49..780e066 100644 --- a/idioms/pass-var-to-closure.html +++ b/idioms/pass-var-to-closure.html @@ -95,7 +95,7 @@ diff --git a/idioms/priv-extend.html b/idioms/priv-extend.html index 3208c60..e435489 100644 --- a/idioms/priv-extend.html +++ b/idioms/priv-extend.html @@ -95,7 +95,7 @@ diff --git a/idioms/rustdoc-init.html b/idioms/rustdoc-init.html index 8afc466..451f3bc 100644 --- a/idioms/rustdoc-init.html +++ b/idioms/rustdoc-init.html @@ -95,7 +95,7 @@ diff --git a/idioms/temporary-mutability.html b/idioms/temporary-mutability.html index 290be2e..31054ee 100644 --- a/idioms/temporary-mutability.html +++ b/idioms/temporary-mutability.html @@ -95,7 +95,7 @@ diff --git a/index.html b/index.html index f1c9a8c..c596462 100644 --- a/index.html +++ b/index.html @@ -95,7 +95,7 @@ diff --git a/intro.html b/intro.html index 20704b9..470d923 100644 --- a/intro.html +++ b/intro.html @@ -95,7 +95,7 @@ diff --git a/patterns/RAII.html b/patterns/RAII.html index cbfc318..1c03d2e 100644 --- a/patterns/RAII.html +++ b/patterns/RAII.html @@ -95,7 +95,7 @@ diff --git a/patterns/builder.html b/patterns/builder.html index 4639efb..d2fcbc2 100644 --- a/patterns/builder.html +++ b/patterns/builder.html @@ -95,7 +95,7 @@ diff --git a/patterns/compose-structs.html b/patterns/compose-structs.html index 326fd34..c1affac 100644 --- a/patterns/compose-structs.html +++ b/patterns/compose-structs.html @@ -95,7 +95,7 @@ diff --git a/patterns/entry.html b/patterns/entry.html index 1f19f6d..7c8ec3b 100644 --- a/patterns/entry.html +++ b/patterns/entry.html @@ -95,7 +95,7 @@ diff --git a/patterns/ffi-export.html b/patterns/ffi-export.html index 8a6052e..4341c5b 100644 --- a/patterns/ffi-export.html +++ b/patterns/ffi-export.html @@ -95,7 +95,7 @@ diff --git a/patterns/ffi-intro.html b/patterns/ffi-intro.html index 9a12ee5..19a0d8d 100644 --- a/patterns/ffi-intro.html +++ b/patterns/ffi-intro.html @@ -95,7 +95,7 @@ diff --git a/patterns/ffi-wrappers.html b/patterns/ffi-wrappers.html index b06dc02..8974701 100644 --- a/patterns/ffi-wrappers.html +++ b/patterns/ffi-wrappers.html @@ -95,7 +95,7 @@ diff --git a/patterns/fold.html b/patterns/fold.html index 865b7cc..faf72e9 100644 --- a/patterns/fold.html +++ b/patterns/fold.html @@ -95,7 +95,7 @@ diff --git a/patterns/index.html b/patterns/index.html index b562d64..1bd1668 100644 --- a/patterns/index.html +++ b/patterns/index.html @@ -95,7 +95,7 @@ diff --git a/patterns/newtype.html b/patterns/newtype.html index 672d4e8..bacc9c5 100644 --- a/patterns/newtype.html +++ b/patterns/newtype.html @@ -95,7 +95,7 @@ diff --git a/patterns/small-crates.html b/patterns/small-crates.html index d9b9030..6562f3f 100644 --- a/patterns/small-crates.html +++ b/patterns/small-crates.html @@ -95,7 +95,7 @@ @@ -209,7 +209,7 @@ an HTTP client that uses url:0.5 would not accept Url - @@ -227,7 +227,7 @@ an HTTP client that uses url:0.5 would not accept Url - diff --git a/patterns/strategy.html b/patterns/strategy.html new file mode 100644 index 0000000..f5eff99 --- /dev/null +++ b/patterns/strategy.html @@ -0,0 +1,391 @@ + + + + + + Strategy - Rust Design Patterns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + +
+
+

Strategy (aka Policy)

+

Description

+

The Strategy design pattern +is a technique that enables separation of concerns. +It also allows to decouple software modules through Dependency Inversion.

+

The basic idea behind the Strategy pattern is that, 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.

+

In this way, a client using the algorithm may choose a specific implementation, while the general algorithm workflow remains the same. +In other words, the abstract specification of the class does not depend on the specific implementation of the derived class, +but specific implementation must adhere to the abstract specification. +This is why we call it "Dependency Inversion".

+

Motivation

+

Imagine we are working on a project that generates reports every month. +We need the reports to be generated in different formats (strategies), e.g., +in JSON or Plain Text formats. +But things vary over time and we don't know what kind of requirement we may get in the future. +For example, we may need to generate our report in a completly new format, +or just modify one of the existing formats.

+

Example

+

In this example our invariants (or abstractions) are Context, Formatter, and Report, +while Text and Json are our strategy structs. +These strategies have to implement the Formatter trait.

+
use std::collections::HashMap;
+type Data = HashMap<String, u32>;
+
+trait Formatter {
+    fn format(&self, data: &Data, s: &mut String);
+}
+
+struct Report;
+
+impl Report {
+    fn generate<T: Formatter>(g: T, s: &mut String) {
+        // backend operations...
+        let mut data = HashMap::new();
+        data.insert("one".to_string(), 1);
+        data.insert("two".to_string(), 2);
+        // generate report
+        g.format(&data, s);
+    }
+}
+
+struct Text;
+impl Formatter for Text {
+    fn format(&self, data: &Data, s: &mut String) {
+        *s = data
+            .iter()
+            .map(|(key, val)| format!("{} {}\n", key, val))
+            .collect();
+    }
+}
+
+struct Json;
+impl Formatter for Json {
+    fn format(&self, data: &Data, s: &mut String) {
+        *s = String::from("[");
+        let mut iter = data.into_iter();
+        if let Some((key, val)) = iter.next() {
+            let entry = format!(r#"{{"{}":"{}"}}"#, key, val);
+            s.push_str(&entry);
+            while let Some((key, val)) = iter.next() {
+                s.push(',');
+                let entry = format!(r#"{{"{}":"{}"}}"#, key, val);
+                s.push_str(&entry);
+            }
+        }
+        s.push(']');
+    }
+}
+
+fn main() {
+    let mut s = String::from("");
+    Report::generate(Text, &mut s);
+    assert!(s.contains("one 1"));
+    assert!(s.contains("two 2"));
+
+    Report::generate(Json, &mut s);
+    assert!(s.contains(r#"{"one":"1"}"#));
+    assert!(s.contains(r#"{"two":"2"}"#));
+}
+
+

Advantages

+

The main advantage is separation of concerns. For example, in this case Report does not know anything about specific +implementations of Json and Text, whereas the output implementations does not care about how data is +preprocessed, stored, and fetched. +The only thing they have to know is context and a specific trait and method to implement, +i.e,Formatter and run.

+

Disadvantages

+

For each strategy there must be implemented at least one module, so number of modules +increases with number of strategies. +If there are many strategies to choose from then users have to know how strategies differ +from one another.

+

Discussion

+

In the previous example all strategies are implemented in a single file. +Ways of providing different strategies includes:

+
    +
  • All in one file (as shown in this example, similar to being separated as modules)
  • +
  • Separated as modules, E.g. formatter::json module, formatter::text module
  • +
  • Use compiler feature flags, E.g. json feature, text feature
  • +
  • Separated as crates, E.g. json crate, text crate
  • +
+

Serde crate is a good example of the Strategy pattern in action. Serde allows full customization +of the serialization behavior by manually implementing Serialize and Deserialize traits for our type. +For example, we could easily swap serde_json with serde_cbor since they expose similar methods. +Having this makes the helper crate serde_transcode much more useful and ergonomic.

+

However, we don't need to use traits in order to design this pattern in Rust.

+

The following toy example demonstrates the idea of the Strategy pattern using Rust +closures:

+
struct Adder;
+impl Adder {
+    pub fn add<F>(x: u8, y: u8, f: F) -> u8
+    where
+        F: Fn(u8, u8) -> u8,
+    {
+        f(x, y)
+    }
+}
+
+fn main() {
+    let arith_adder = |x, y| x + y;
+    let bool_adder = |x, y| {
+        if x == 1 || y == 1 {
+            1
+        } else {
+            0
+        }
+    };
+    let custom_adder = |x, y| 2 * x + y;
+
+    assert_eq!(9, Adder::add(4, 5, arith_adder));
+    assert_eq!(0, Adder::add(0, 0, bool_adder));
+    assert_eq!(5, Adder::add(1, 3, custom_adder));
+}
+
+
+

In fact, Rust already uses this idea for Options's map method:

+
fn main() {
+    let val = Some("Rust");
+
+    let len_strategy = |s: &str| s.len();
+    assert_eq!(4, val.map(len_strategy).unwrap());
+
+    let first_byte_strategy = |s: &str| s.bytes().next().unwrap();
+    assert_eq!(82, val.map(first_byte_strategy).unwrap());
+}
+
+

See also

+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patterns/unsafe-mods.html b/patterns/unsafe-mods.html index b87ac26..883b439 100644 --- a/patterns/unsafe-mods.html +++ b/patterns/unsafe-mods.html @@ -95,7 +95,7 @@ @@ -200,7 +200,7 @@ However, users have the option of using an unsafe method to create