Skip to content

Commit 4c0d4a0

Browse files
committed
Improve Flyweight with more documentation
1 parent 7d10174 commit 4c0d4a0

File tree

4 files changed

+65
-21
lines changed

4 files changed

+65
-21
lines changed

structural/flyweight/README.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Flyweight
22

3-
_**Flyweight** is a structural design pattern that allows programs to support
4-
ast quantities of objects by keeping their memory consumption low._
3+
_**Flyweight** (Cache) is a structural design pattern that allows programs to
4+
support vast quantities of objects by keeping their memory consumption low._
5+
6+
It's an _internal cache_ hidden behind a settle Facade-like API.
7+
The cache stores shared parts that are referenced from multiple objects.
8+
_See more explanation below._
59

6-
The pattern achieves it by sharing parts of object state between multiple
7-
objects.
810

911
## Rendering a Forest
1012

@@ -24,25 +26,47 @@ For 100,000 trees:
2426

2527
```
2628
100000 trees drawn
27-
---------------------
29+
Cache length: 2 tree kinds
30+
-------------------------------
2831
Memory usage:
2932
Tree size (16 bytes) * 100000
3033
+ TreeKind size (~30 bytes) * 2
31-
---------------------
34+
-------------------------------
3235
Total: 1MB (instead of 4MB)
3336
```
3437

35-
Flyweight is implemented via a `HashMap` that holds only one copy
36-
of a `TreeKind` for each `Tree` inside of the `Forest` structure.
38+
## Overview
39+
40+
`Forest` has a public API that can not be changed for backward compatibility reasons:
41+
42+
```rust
43+
pub fn plant_tree(&mut self, x: u32, y: u32, color: TreeColor, name: String, data: String);
44+
```
45+
46+
There is an internal cache that is implemented via a `HashSet` that holds
47+
only one copy of a common part (`TreeKind`) of thousands of trees.
3748

3849
```rust
3950
#[derive(Default)]
4051
pub struct Forest {
41-
pub tree_kinds: HashMap<String, Rc<TreeKind>>,
42-
pub trees: Vec<Tree>,
52+
cache: HashSet<Rc<TreeKind>>,
53+
trees: Vec<Tree>,
4354
}
4455
```
4556

57+
The point is having an opaque cache implementation. It can use a hash set,
58+
FIFO, or even a simple vector. And it's hidden behind the API because that's
59+
the point: we try to optimize internals without changing a public method,
60+
otherwise we could always pass a common part from top to bottom.
61+
62+
Other points:
63+
- `cache` is of `HashSet` type, so it can hold only a single
64+
instance of a `TreeKind`,
65+
- `Rc` is needed to get the reference on the tree kind without
66+
cloning a full structure,
67+
- `TreeKind` must derive `Eq`, `PartialEq`, and `Hash` traits to be
68+
used in the `HashSet`.
69+
4670
## Reference
4771

4872
The example reproduces a [Flyweight Example in Java (Rendering a Forest)](https://refactoring.guru/design-patterns/flyweight/java/example).

structural/flyweight/forest.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,40 @@
11
mod tree;
22

33
use draw::Canvas;
4-
use std::{collections::HashMap, rc::Rc};
4+
use std::{collections::HashSet, rc::Rc};
55
use tree::{Tree, TreeKind};
66

77
pub use self::tree::TreeColor;
88

9+
/// Forest implements an internal cache that is hidden behind the public API.
10+
///
11+
/// The point is having an opaque cache implementation. It can use a hash set,
12+
/// FIFO, or even a simple vector.
13+
///
14+
/// Here are the key points:
15+
/// - `cache` is of `HashSet` type, so it can hold only a single
16+
/// instance of a `TreeKind`,
17+
/// - `Rc` is needed to get the reference on the tree kind without
18+
/// cloning the full structure,
19+
/// - `TreeKind` must derive `Eq`, `PartialEq`, and `Hash` traits to be
20+
/// used in the `HashSet`.
921
#[derive(Default)]
1022
pub struct Forest {
11-
pub tree_kinds: HashMap<String, Rc<TreeKind>>,
12-
pub trees: Vec<Tree>,
23+
cache: HashSet<Rc<TreeKind>>,
24+
trees: Vec<Tree>,
1325
}
1426

1527
impl Forest {
1628
pub fn plant_tree(&mut self, x: u32, y: u32, color: TreeColor, name: String, data: String) {
17-
// Here is the substance of the Flywheight Pattern:
29+
let tree_kind = TreeKind::new(color, name.clone(), data);
30+
31+
// Here is an essence of Flyweight: it's an internal cache,
1832
// there is always a single instance of a "tree kind" structure.
19-
let tree_kind = self
20-
.tree_kinds
21-
.entry(name.clone())
22-
.or_insert(Rc::new(TreeKind::new(color, name.clone(), data)));
33+
self.cache.insert(Rc::new(tree_kind.clone()));
2334

2435
// A tree kind is referenced from each tree instance using `Rc` pointer.
2536
// `tree_kind.clone()` increases a reference counter instead of real cloning.
26-
let tree = Tree::new(x, y, tree_kind.clone());
37+
let tree = Tree::new(x, y, self.cache.get(&tree_kind).unwrap().clone());
2738
self.trees.push(tree);
2839
}
2940

@@ -32,4 +43,8 @@ impl Forest {
3243
tree.draw(canvas);
3344
}
3445
}
46+
47+
pub fn cache_len(&self) -> usize {
48+
self.cache.len()
49+
}
3550
}

structural/flyweight/forest/tree.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::rc::Rc;
22

33
use draw::{Canvas, Drawing, Shape, Style, RGB};
44

5+
#[derive(Clone, PartialEq, Eq, Hash)]
56
pub enum TreeColor {
67
Color1,
78
Color2,
@@ -18,6 +19,9 @@ impl TreeColor {
1819
}
1920
}
2021

22+
/// A cacheable item. It derives `PartialEq`, `Eq`, and `Hash` in order to be
23+
/// used in the `HashSet`.
24+
#[derive(Clone, PartialEq, Eq, Hash)]
2125
pub struct TreeKind {
2226
color: TreeColor,
2327
_name: String,

structural/flyweight/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ fn main() {
3838
render::save(&canvas, "res/forest.svg", SvgRenderer::new()).expect("Rendering");
3939

4040
println!("{} trees drawn", TREES_TO_DRAW);
41-
println!("---------------------");
41+
println!("Cache length: {} tree kinds", forest.cache_len());
42+
println!("-------------------------------");
4243
println!("Memory usage:");
4344
println!("Tree size (16 bytes) * {}", TREES_TO_DRAW);
4445
println!("+ TreeKind size (~30 bytes) * {}", TREE_TYPES);
45-
println!("---------------------");
46+
println!("-------------------------------");
4647
println!(
4748
"Total: {}MB (instead of {}MB)",
4849
((TREES_TO_DRAW * 16 + TREE_TYPES * 30) / 1024 / 1024),

0 commit comments

Comments
 (0)