Design Pattern (Rust Edition)

Rust 是一门现代的系统级编程语言,它以安全、并发和高性能的特性而闻名。作为一位 Rust 开发者,你知道 Rust 的语法和特性,但是知道如何使用 Rust 实现设计模式吗?

设计模式是一种经典的编程思想,由四人帮提出,它提供了解决软件设计问题的通用解决方案。在本文中,我们将介绍 23 种设计模式,并提供 Rust 实现的示例代码。

本文将按照学习设计模式的传统方式,将这些模式分成三大类:创建型模式、结构型模式和行为型模式。每个模式都有定义和特点,我们还将提供 Rust 实现的详细示例和解释。我们深入研究每个模式的细节,分析每个模式的适用场景和优缺点,你将学习如何在 Rust 中使用这些模式。

我们相信,这篇关于 Rust 实现 23 种设计模式的文章,将为 Rust 开发者提供有价值的学习资源,帮助他们更好地理解 Rust 的设计模式和编程实践。让我们一起深入研究这些设计模式,掌握 Rust 的高级编程技术!

创建型模式

工厂模式(Factory Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 创建一个 trait,规范工厂方法和产品类型
trait Shape {
fn draw(&self);
}

// 创建两种产品类型:Rectangle 和 Circle
struct Rectangle;

impl Shape for Rectangle {
fn draw(&self) {
println!("Drawing a rectangle");
}
}

struct Circle;

impl Shape for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}

// 创建一个工厂,实现工厂方法
enum ShapeType {
Rectangle,
Circle,
}

struct ShapeFactory;

impl ShapeFactory {
fn get_shape(&self, shape_type: ShapeType) -> Box<dyn Shape> {
match shape_type {
ShapeType::Rectangle => Box::new(Rectangle),
ShapeType::Circle => Box::new(Circle),
}
}
}

// 在主函数中调用工厂方法
fn main() {
let shape_factory = ShapeFactory;
let shape1 = shape_factory.get_shape(ShapeType::Rectangle);
let shape2 = shape_factory.get_shape(ShapeType::Circle);
shape1.draw();
shape2.draw();
}

上面的代码演示了一个简单的工厂模式实现,创建了两种不同类型的 Shape,即 Rectangle 和 Circle。然后定义了一个工厂 ShapeFactory,可以根据不同的类型创建不同的 Shape 实例。

在主函数中调用工厂方法,通过 ShapeType 参数来指定需要创建的 Shape 类型。最后,将工厂方法返回的对象使用 Box 包装,以便进行类型抹消,并调用其 draw 方法来执行具体的绘制操作。

抽象工厂模式(Abstract Factory Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 创建抽象工厂接口
trait AbstractFactory {
fn create_shape(&self) -> Box<dyn Shape>;
fn create_color(&self) -> Box<dyn Color>;
}

// 创建具体工厂_1,与一组具体的产品(形状和颜色)相关
struct ConcreteFactory1;

impl AbstractFactory for ConcreteFactory1 {
fn create_shape(&self) -> Box<dyn Shape> {
Box::new(Rectangle)
}

fn create_color(&self) -> Box<dyn Color> {
Box::new(Red)
}
}

// 创建具体工厂_2,与一组具体的产品(形状和颜色)相关
struct ConcreteFactory2;

impl AbstractFactory for ConcreteFactory2 {
fn create_shape(&self) -> Box<dyn Shape> {
Box::new(Circle)
}

fn create_color(&self) -> Box<dyn Color> {
Box::new(Blue)
}
}

// 创建抽象产品:形状和颜色接口
trait Shape {
fn draw(&self);
}

trait Color {
fn fill(&self);
}

// 创建具体产品:矩形,圆形,红色和蓝色
struct Rectangle;

impl Shape for Rectangle {
fn draw(&self) {
println!("Draw a rectangle.")
}
}

struct Circle;

impl Shape for Circle {
fn draw(&self) {
println!("Draw a circle.")
}
}

struct Red;

impl Color for Red {
fn fill(&self) {
println!("Fill color red.")
}
}

struct Blue;

impl Color for Blue {
fn fill(&self) {
println!("Fill color blue.")
}
}

// 在 main 函数中实例化并使用具体工厂和产品
fn main() {
let factory1 = ConcreteFactory1;
let shape1 = factory1.create_shape();
let color1 = factory1.create_color();
shape1.draw();
color1.fill();

let factory2 = ConcreteFactory2;
let shape2 = factory2.create_shape();
let color2 = factory2.create_color();
shape2.draw();
color2.fill();
}

在上面的示例代码中,抽象工厂接口 AbstractFactory 定义了两个抽象方法,用于创建形状和颜色。具体工厂 ConcreteFactory1ConcreteFactory2 分别实现了抽象工厂接口,用于实例化具体的产品。产品由抽象产品 ShapeColor 定义,矩形,圆形,红色和蓝色则是具体产品的实现。

main 函数中,我们实例化了具体工厂和产品,并调用了它们的方法。在此示例中,ConcreteFactory1 创建的是矩形和红色,而 ConcreteFactory2 创建的是圆和蓝色。

单例模式(Singleton Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#[macro_use]
extern crate lazy_static;
use std::sync::{Mutex, Once};

lazy_static! {
static ref INSTANCE: Mutex<Singleton> = Mutex::new(Singleton::new());
}

struct Singleton {
count: i32,
}

impl Singleton {
fn new() -> Self {
Singleton {
count: 0,
}
}

fn get_instance() -> &'static Mutex<Singleton> {
&*INSTANCE
}

fn increment_count(&mut self) {
self.count += 1;
}
}

fn main() {
let mut s1 = Singleton::get_instance().lock().unwrap();
let mut s2 = Singleton::get_instance().lock().unwrap();

s1.increment_count();
s2.increment_count();

// 获取 MutexGuard 引用的方式,直接访问 MutexGuard 中数据
println!("Count: {}", s2.count); // Output: "Count: 2"
}

在这个例子中,我们定义了一个 Singleton 结构体,其中包含一个计数器 count,使用 lazy_static 宏实现单例模式。在 Singleton 中,我们定义了一个静态方法 get_instance 用于获取 Singleton 实例的引用。在 get_instance 方法中,我们使用 &*INSTANCE 获取 Mutex 对象的锁,然后使用锁保护的 Singleton 实例。由于 INSTANCE 是 Mutex 类型的静态变量,所以可以使用 'static 生命周期来限制该单例实例的生命周期。

在 main 方法中,我们获取 Singleton 实例的两个可变引用 s1 和 s2,并调用 increment_count 方法增加 count 的值。在两次获取 Singleton 实例的过程中,我们使用了 Mutex 对象的锁来保护 Singleton 实例,以确保只有一个线程可以修改计数器的值。最后,我们使用 MutexGuard 引用中包含的数据输出计数器的值。

请注意,为了更好地保护 Singleton 实例,我们可以将 Mutex 对象的关联类型指定为 Once,以确保在 Mutex 对象的生命周期中只执行一次初始化过程,具体实现请留待用户自行优化。此外,由于 Singleton 实例是被 Mutex 对象的锁保护的,因此在访问 Singleton 实例时需要获取 Mutex 对象的锁。

建造者模式(Builder Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#[derive(Debug)]
struct Person {
name: String,
age: u8,
address: String,
}

struct PersonBuilder {
name: Option<String>,
age: Option<u8>,
address: Option<String>,
}

impl PersonBuilder {
fn new() -> Self {
Self {
name: None,
age: None,
address: None,
}
}

fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(name.to_string());
self
}

fn age(&mut self, age: u8) -> &mut Self {
self.age = Some(age);
self
}

fn address(&mut self, address: &str) -> &mut Self {
self.address = Some(address.to_string());
self
}

fn build(&mut self) -> Result<Person, String> {
let name = self.name.take().ok_or("name is missing")?;
let age = self.age.take().ok_or("age is missing")?;
let address = self.address.take().ok_or("address is missing")?;

Ok(Person { name, age, address })
}
}

fn main() {
let person = PersonBuilder::new()
.name("Alice")
.age(25)
.address("123 Main St.")
.build()
.unwrap();

println!("{:#?}", person);
}

在上面的例子中,我们定义了一个PersonBuilder结构体和相应的方法。Person结构体表示一个人,包含名字、年龄和地址。PersonBuilder结构体包含三个可选字段:名字、年龄和地址。默认情况下,它们都是None。

接下来,我们实现了一些方法来设置每个字段的值。这些方法都返回一个可变引用,以便链式调用。

最后,我们添加了一个build方法,该方法从PersonBuilder中生成一个Person实例。如果任何必需字段的值不存在,则build方法将返回一个错误。

在main函数中,我们使用Builder模式创建一个Person实例,并将其打印出来。

这个例子只是一个简单的例子,但它演示了如何使用Rust实现Builder模式。在实际项目中,我们可能会包含更多的字段和方法,以及一些较复杂的验证逻辑。

原型模式(Prototype Pattern)

原型模式在Rust中的实现可以通过实现Clone trait来实现。让我们假设我们有一个结构体Person,含有nameage两个字段,我们将使用它来演示如何在Rust中实现原型模式:

1
2
3
4
5
6
7
8
9
10
11
12
#[derive(Clone)]
struct Person {
name: String,
age: u8,
}

impl Person {
fn info(&self) {
println!("Name: {}, Age: {}", self.name, self.age);
}
}

接下来我们将创建一个PrototypeManager结构体,它包含一个名称为prototypes的哈希表(HashMap),哈希表的键类型是一个字符串,值类型是Rc<Person>。这里使用了Rc(Reference Counting)来管理Person对象,因为它是一个共享所有权的智能指针。

1
2
3
4
5
6
7
use std::collections::HashMap;
use std::rc::Rc;

struct PrototypeManager {
prototypes: HashMap<String, Rc<Person>>,
}

现在我们将为PrototypeManager实现两个方法,一个是用于在哈希表中添加一个新的原型对象,另一个是用于获取一个对象的副本。要添加一个新原型,我们只需要在哈希表中插入一个键值对,其中键是原型的名称,值是原型对象的Rc指针。获取对象的副本则需要早先添加原型方法中保存的原型对象的克隆,使用Rc的clone方法复制哈希表中的Rc指针,然后从中取出引用的Person对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
impl PrototypeManager {
fn new() -> Self {
Self {
prototypes: HashMap::new(),
}
}

fn add(&mut self, name: &str, prototype: Person) {
self.prototypes.insert(name.to_string(), Rc::new(prototype));
}

fn get(&self, name: &str) -> Option<Rc<Person>> {
self.prototypes.get(name).map(|p| p.clone())
}
}

现在,我们可以创建原型管理器并添加原型对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let mut manager = PrototypeManager::new();

let john = Person { name: "John".to_string(), age: 30 };
let jane = Person { name: "Jane".to_string(), age: 25 };

manager.add("john", john.clone());
manager.add("jane", jane.clone());

let john_clone = manager.get("john").unwrap();
let jane_clone = manager.get("jane").unwrap();

john_clone.info();
jane_clone.info();
}

在上面的例子中,我们创建一个名为manager的原型管理器,并添加了两个原型对象:johnjane。我们接着使用get方法从manager中获取john和jane的副本,并使用info方法打印它们的名称和年龄。

输出如下:

1
2
3
Name: John, Age: 30
Name: Jane, Age: 25

这就是在Rust中使用原型模式的基本方法。与传统的原型模式不同,Rust的clone方法允许我们轻松地克隆任何复杂度的对象,包括堆栈和堆上的数据。这使得Rust成为实现原型模式的理想语言。

结构型模式

适配器模式(Adapter Pattern)

适配器模式用于将现有的接口(或类)转换为另一个需要的接口(或类)。在Rust中,可以使用包装器或代理对象来实现此目的。在本例中,我们将实现一个适配器,用于将Rectangle结构体的行为适应为与一个Shape接口一致。

首先,我们来定义Shape接口以及初始的Rectangle结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait Shape {
fn draw(&self);
}

struct Rectangle {
width: f64,
height: f64
}

impl Rectangle {
fn new(width: f64, height: f64) -> Self {
Self {
width,
height
}
}

fn area(&self) -> f64 {
self.width * self.height
}
}

接下来,我们编写适配器,使得Rectangle实现Shape接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct RectangleAdapter {
rectangle: Rectangle
}

impl RectangleAdapter {
fn new(rectangle: Rectangle) -> Self {
Self { rectangle }
}
}

impl Shape for RectangleAdapter {
fn draw(&self) {
println!("Drawing rectangle with area: {}", self.rectangle.area());
}
}

适配器RectangleAdapter包含一个Rectangle对象,并实现了Shape接口,用于将Rectangle的行为适应为Shape的行为。在draw方法中,适配器将Rectangle的面积打印出来,模拟了“绘制矩形”的行为。

最后,我们来测试适配器模式。在main函数中,创建一个Rectangle对象和一个RectangleAdapter对象,并调用它们的areadraw方法:

1
2
3
4
5
6
7
8
9
fn main() {
let rectangle = Rectangle::new(4.0, 5.0);
let rectangle_adapter = RectangleAdapter::new(rectangle);

println!("Rectangle area: {}", rectangle.area());

rectangle_adapter.draw();
}

输出结果如下:

1
2
3
Rectangle area: 20
Drawing rectangle with area: 20

我们可以看到,Rectangle对象的area方法和RectangleAdapter对象的draw方法都可以正常工作,且结果一致。适配器模式使我们能够将不兼容的接口转化为兼容的接口,简化了代码的开发和维护。

桥接模式(Bridge Pattern)

桥接模式是一种将抽象部分与实现部分分离的设计模式,在 Rust 中可以通过 trait 和泛型参数来实现。在本例中,我们将实现一个桥接模式,用于将不同形状的图形对象(例如,圆形和矩形)的绘制方法与不同颜色的图形对象(例如,红色和蓝色)的填充方法进行分离。

首先,我们定义 ShapeColor 两个 trait:

1
2
3
4
5
6
7
trait Shape {
fn draw(&self, color: &dyn Color);
}

trait Color {
fn fill(&self);
}

上述代码定义了 Shape trait 和 Color trait。通过 draw 方法,我们可以给图形对象设置颜色进行绘制,在方法参数中接收 Color trait 的实例,由实现了这个 trait 的对象进行颜色的填充操作。

接下来,我们来编写不同形状的图形对象:圆形和矩形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Circle {
radius: f64
}

struct Rectangle {
width: f64,
height: f64
}

impl Shape for Circle {
fn draw(&self, color: &dyn Color) {
println!("Drawing circle with radius {} and filled with color:", self.radius);
color.fill();
}
}

impl Shape for Rectangle {
fn draw(&self, color: &dyn Color) {
println!("Drawing rectangle with width {} and height {} and filled with color:", self.width, self.height);
color.fill();
}
}

上面的代码实现了 Shape trait 的 draw 方法,分别用于在适当颜色下绘制圆和矩形,由 color 参数指定填充的颜色。

最后我们来编写颜色实现,这里编写红色和蓝色的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct RedColor;

impl Color for RedColor {
fn fill(&self) {
println!("Red color filled.");
}
}

struct BlueColor;

impl Color for BlueColor {
fn fill(&self) {
println!("Blue color filled.");
}
}

上面的代码实现了 Color trait 的 fill 方法,分别用于填充红色和蓝色。

现在我们来定义一个函数,用于绘制圆形和矩形并指定颜色:

1
2
3
4
fn draw_shape(shape: &dyn Shape, color: &dyn Color) {
shape.draw(color);
}

这个函数是一个桥接函数,它将形状对象和颜色对象进行了分离,接收一个 Shape trait 和 Color trait 的实现对象,并分别传递给了 draw 方法和 fill 方法,从而实现了形状和颜色的分离。

最后,我们来测试桥接模式,在 main 函数中,创建一个圆形和一个矩形对象,以及一个红色和一个蓝色对象,并分别调用 draw_shape 函数绘制它们:

1
2
3
4
5
6
7
8
9
10
fn main() {
let circle = Circle { radius: 5.0 };
let rectangle = Rectangle { width: 4.0, height: 5.0 };
let red_color = RedColor {};
let blue_color = BlueColor {};

draw_shape(&circle, &red_color);
draw_shape(&rectangle, &blue_color);
}

上面的代码将输出以下内容:

1
2
3
4
Drawing circle with radius 5 and filled with color:
Red color filled.
Drawing rectangle with width 4 and height 5 and filled with color:
Blue color filled.

代理模式(Proxy Pattern)

代理模式是一种结构型设计模式,它允许你提供一个可以代表其他对象的代理对象。在 Rust 中,我们可以通过实现一个包含原始对象(也称为 RealSubject)的代理对象(也称为 Proxy)来实现代理模式。

在本例中,我们将实现一个文件读取器,此读取器支持从本地磁盘和网络中读取文件。我们将使用代理模式将网络读取器包装在本地读取器中,并提供相同的公共接口。

首先定义文件读取器的 trait,其中包含读取文件内容的方法:

1
2
3
trait Reader {
fn read(&self) -> String;
}

接下来创建一个“真实”文件读取器,该文件读取器代表从本地磁盘中读取文件的功能:

1
2
3
4
5
6
7
8
9
10
11
struct LocalReader {
file_path: String,
}

impl Reader for LocalReader {
fn read(&self) -> String {
println!("Reading file from local disk: {}", self.file_path);
// 实际从文件中读取内容
String::from("Local file content")
}
}

然后,我们实现代理类 ProxyReader,该类实现 Reader trait。代理类将决定调用哪个 Reader 实例,也就是它可以代表或包装 RealSubject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct ProxyReader {
local_reader: LocalReader,
network_reader: NetworkReader, // 实际读取网络内容的对象
is_network: bool,
}

impl Reader for ProxyReader {
fn read(&self) -> String {
if self.is_network {
println!("Reading file from network");
self.network_reader.read()
} else {
println!("Reading file from local disk");
self.local_reader.read()
}
}
}

代理类根据 is_network 的值决定读取哪个文件,如果为true,则读取网络文件,如果为false,则读取本地文件。

最后,定义网络文件读取器以及一些测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct NetworkReader;

impl Reader for NetworkReader {
fn read(&self) -> String {
println!("Reading file from network");
// 实际从网络读取文件内容
String::from("Network file content")
}
}

fn main() {
let local_reader = LocalReader {
file_path: String::from("example.txt"),
};
let network_reader = NetworkReader;
let proxy_reader = ProxyReader {
is_network: true,
local_reader,
network_reader,
};
println!("{}", proxy_reader.read());
}

在上面的测试代码中,我们将请求委托给代理 ProxyReader 类,该类将确定读取哪个文件,并调用相应的 Reader 实例的 read 方法。

最终,运行程序会输出以下内容:

1
2
3
Reading file from network
Reading file from network
Network file content

说明代理模式已经被成功实现。

以下是代码生成的PlantUML图:

组合模式(Composite Pattern)

组合模式是一种结构型设计模式,它将对象组合成树状结构,以表示部分整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。在 Rust 中,我们可以使用 trait 和结构体实现组合模式。

在本例中,我们将创建一个简单的文件系统,包括文件和文件夹,文件夹可以包含子文件夹和文件。我们将使用组合模式将文件和文件夹结合成树形结构。

首先定义一个 Component trait 来表示文件和文件夹共有的属性和方法:

1
2
3
4
5
6
7
trait Component {
fn get_name(&self) -> &str;
fn get_size(&self) -> u64;
fn add(&mut self, component: Box<dyn Component>);
fn remove(&mut self, name: &str);
fn display(&self);
}

其中,get_name 方法和 get_size 方法是获取文件或文件夹的名称和大小的方法。此外,我们还为文件夹添加了 addremove 方法,这些方法用于向文件夹添加或删除子文件或子文件夹。最后,我们还添加了一个 display 方法,用于将文件系统的内容输出到控制台上。

接下来,我们创建一个 File 类来表示文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct File {
name: String,
size: u64,
}

impl Component for File {
fn get_name(&self) -> &str {
&self.name
}

fn get_size(&self) -> u64 {
self.size
}

fn add(&mut self, _: Box<dyn Component>) {
panic!("Cannot add to a file");
}

fn remove(&mut self, _: &str) {
panic!("Cannot remove from a file");
}

fn display(&self) {
println!("{} ({} bytes)", self.name, self.size);
}
}

为了方便,这里我们将文件和文件夹都实现 Component trait。

接下来,我们创建一个 Folder 类来表示文件夹。由于文件夹可以包含子文件夹和子文件,我们在 Folder 类的实现中需要增加对添加和删除操作的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct Folder {
name: String,
components: Vec<Box<dyn Component>>,
}

impl Folder {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
components: vec![],
}
}
}

impl Component for Folder {
fn get_name(&self) -> &str {
&self.name
}

fn get_size(&self) -> u64 {
let mut total_size = 0;
for component in &self.components {
total_size += component.get_size();
}
total_size
}

fn add(&mut self, component: Box<dyn Component>) {
self.components.push(component);
}

fn remove(&mut self, name: &str) {
let index = self
.components
.iter()
.position(|component| component.get_name() == name)
.expect("Component not found");
self.components.remove(index);
}

fn display(&self) {
println!("{} ({} bytes)", self.name, self.get_size());
for component in &self.components {
component.display();
}
}
}

在这个类中,我们通过一个 Vec<Box<dyn Component>> 属性来表示文件夹中包含的子文件或子文件夹。而在实现中,我们对成员变量进行了初始化操作,这在 Rust 中是必须的。

最后,添加一些测试代码,以验证组合模式的实现是否正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
fn main() {
let mut root = Folder::new("Root");
let mut documents = Folder::new("Documents");
let mut pictures = Folder::new("Pictures");

let file1 = File {
name: String::from("document1.txt"),
size: 100,
};
let file2 = File {
name: String::from("document2.txt"),
size: 200,
};
let file3 = File {
name: String::from("picture1.jpg"),
size: 300,
};
let file4 = File {
name: String::from("picture2.png"),
size: 400,
};

// 添加文件或子文件夹
documents.add(Box::new(file1));
documents.add(Box::new(file2));
pictures.add(Box::new(file3));
pictures.add(Box::new(file4));
root.add(Box::new(documents));
root.add(Box::new(pictures));

// 显示文件系统信息
root.display();
println!("Folder size: {} bytes", root.get_size());
}

运行程序,最终输出以下内容:

1
2
3
4
5
6
7
8
Root (1000 bytes)
Documents (300 bytes)
document1.txt (100 bytes)
document2.txt (200 bytes)
Pictures (700 bytes)
picture1.jpg (300 bytes)
picture2.png (400 bytes)
Folder size: 1000 bytes

装饰器模式(Decorator Pattern)

装饰器模式是一种结构型设计模式,它允许您在不更改对象原始接口的情况下添加行为。在 Rust 中,我们可以借助 trait 实现装饰器模式。

在本例中,我们将实现一个简单的咖啡店,客户可以点不同种类的咖啡,并可以添加各种不同的配料来制作自己喜欢的咖啡。我们将用装饰器模式来实现通过添加不同的配料来定制咖啡。

首先,定义一个 Coffee trait 来表示咖啡的基本属性,包括描述和价格:

1
2
3
4
trait Coffee {
fn get_description(&self) -> String;
fn get_cost(&self) -> f32;
}

接下来,实现一个 SimpleCoffee 结构体表示基本咖啡:

1
2
3
4
5
6
7
8
9
10
struct SimpleCoffee;
impl Coffee for SimpleCoffee {
fn get_description(&self) -> String {
String::from("Simple Coffee")
}

fn get_cost(&self) -> f32 {
1.0
}
}

SimpleCoffee 实现了 Coffee trait,并实现了 get_descriptionget_cost 方法。这里的默认值为 Simple Coffee 和价格为 1.0。

然后,创建一个 CondimentDecorator trait 来表示咖啡配料:

1
2
3
trait CondimentDecorator: Coffee {
fn get_decorated_description(&self) -> String;
}

我们将 CondimentDecorator 扩展了 Coffee trait,其中新增了 get_decorated_description 方法。

接下来,为 Milk 配料创建一个 MilkDecorator 结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct MilkDecorator {
coffee: Box<dyn Coffee>,
}

impl Coffee for MilkDecorator {
fn get_description(&self) -> String {
self.coffee.get_description()
}

fn get_cost(&self) -> f32 {
self.coffee.get_cost() + 0.5
}
}

impl CondimentDecorator for MilkDecorator {
fn get_decorated_description(&self) -> String {
format!("{}, Milk", self.coffee.get_description())
}
}

MilkDecorator 结构体中,我们实现了 Coffee trait,并实现了 get_description 方法和 get_cost 方法。这里,我们使用 Box<dyn Coffee> 来存储咖啡,以便我们可以向咖啡中添加配料。

我们还实现了 CondimentDecorator trait 的 get_decorated_description 方法,它用于获得描述了加了什么配料的咖啡名称。

同样的,我们也创建一个 Sugar 配料的结构体 SugarDecorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct SugarDecorator {
coffee: Box<dyn Coffee>,
}

impl Coffee for SugarDecorator {
fn get_description(&self) -> String {
self.coffee.get_description()
}

fn get_cost(&self) -> f32 {
self.coffee.get_cost() + 0.3
}
}

impl CondimentDecorator for SugarDecorator {
fn get_decorated_description(&self) -> String {
format!("{}, Sugar", self.coffee.get_description())
}
}

在这两个结构体中,我们通过组合的方式来添加咖啡的配料,并在 get_cost 方法中加上配料的价格。

最后,我们在 main 函数中测试装饰器模式的实现:

1
2
3
4
5
6
7
8
9
10
fn main() {
let simple_coffee = SimpleCoffee;
println!("SimpleCoffee: {} (${})", simple_coffee.get_description(), simple_coffee.get_cost());

let milk_coffee = MilkDecorator { coffee: Box::new(simple_coffee) };
println!("MilkCoffee: {} (${})", milk_coffee.get_decorated_description(), milk_coffee.get_cost());

let sugar_milk_coffee = SugarDecorator { coffee: Box::new(milk_coffee) };
println!("SugarMilkCoffee: {} (${})", sugar_milk_coffee.get_decorated_description(), sugar_milk_coffee.get_cost());
}

输出结果如下:

1
2
3
SimpleCoffee: Simple Coffee ($1)
MilkCoffee: Simple Coffee, Milk ($1.5)
SugarMilkCoffee: Simple Coffee, Milk, Sugar ($1.8)

外观模式(Facade Pattern)

外观模式也是一种结构型设计模式,它提供了一个统一的接口,以简化复杂系统中的访问。在 Rust 中,我们可以通过一个结构体来实现外观模式。

在本例中,我们将实现一个机器人控制系统,并使用外观模式来隐藏背后的复杂性。

首先,定义几个用于控制机器人的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct RobotArms;
impl RobotArms {
fn move_left_arm(&self) {
println!("Moving left arm.");
}

fn move_right_arm(&self) {
println!("Moving right arm.");
}
}

struct RobotLegs;
impl RobotLegs {
fn move_left_leg(&self) {
println!("Moving left leg.");
}

fn move_right_leg(&self) {
println!("Moving right leg.");
}
}

struct RobotHead;
impl RobotHead {
fn turn_left(&self) {
println!("Turning head to the left.");
}

fn turn_right(&self) {
println!("Turning head to the right.");
}

fn nod(&self) {
println!("Nodding head.");
}
}

这里我们定义了三个结构体来代表机器人的手臂、腿和头,每个结构体都有不同的方法来控制机器人的动作。

然后,定义一个外观结构体 Robot 来封装这些底层的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct Robot {
arms: RobotArms,
legs: RobotLegs,
head: RobotHead,
}

impl Robot {
fn new() -> Self {
Self {
arms: RobotArms,
legs: RobotLegs,
head: RobotHead,
}
}

fn walk(&self) {
self.legs.move_left_leg();
self.legs.move_right_leg();
}

fn wave(&self) {
self.arms.move_left_arm();
self.arms.move_right_arm();
}

fn nod_head(&self) {
self.head.nod();
}

fn turn_head_left(&self) {
self.head.turn_left();
}

fn turn_head_right(&self) {
self.head.turn_right();
}
}

Robot 结构体中,我们将 RobotArmsRobotLegsRobotHead 封装到一个单一的结构体中。我们通过 new 方法来创建 Robot 对象,并向外界暴露了几个方法,包括 walkwavenod_headturn_head_leftturn_head_right。外部可以通过这些方法来访问机器人的各部分。

最后,我们在 main 函数中创建 Robot 对象,并使用外观模式来控制机器人的动作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let robot = Robot::new();

// 让机器人走路
robot.walk();

// 让机器人挥手
robot.wave();

// 左右摇头
robot.turn_head_left();
robot.turn_head_right();

// 点头
robot.nod_head();
}

输出结果如下:

1
2
3
4
5
6
7
Moving left leg.
Moving right leg.
Moving left arm.
Moving right arm.
Turning head to the left.
Turning head to the right.
Nodding head.

享元模式(Flyweight Pattern)

享元模式是一种结构型设计模式,它的主要目标是减少重复对象的数量,以减少内存占用。在 Rust 中,我们可以使用一个共享结构体来实现享元模式。

在本例中,我们将实现一个类似于文本编辑器的程序,并使用享元模式来共享相同的文本属性(如字体、颜色等)。

首先,定义一个文本属性结构体 TextAttributes,它包含文本属性的所有信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct TextAttributes {
font: String,
font_size: u32,
color: String,
}

impl TextAttributes {
fn new(font: &str, font_size: u32, color: &str) -> Self {
Self {
font: font.to_owned(),
font_size,
color: color.to_owned(),
}
}

fn draw(&self, text: &str) {
println!(
"Drawing text '{}' with font '{}' (size {}) and color '{}'",
text, self.font, self.font_size, self.color,
);
}
}

然后,我们定义一个享元结构体 TextAttributesFactory,它负责创建和共享具有相同属性的 TextAttributes 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::collections::HashMap;

struct TextAttributesFactory {
attributes_map: HashMap<(String, u32, String), TextAttributes>,
}

impl TextAttributesFactory {
fn new() -> Self {
Self {
attributes_map: HashMap::new(),
}
}

fn get_attributes(&mut self, font: &str, font_size: u32, color: &str) -> &TextAttributes {
let key = (font.to_owned(), font_size, color.to_owned());
if let Some(attributes) = self.attributes_map.get(&key) {
attributes
} else {
let attributes = TextAttributes::new(font, font_size, color);
self.attributes_map.insert(key, attributes);
self.attributes_map.get(&key).unwrap()
}
}
}

TextAttributesFactory 中,我们使用一个 HashMap 来保存 TextAttributes 对象,每个对象都具有唯一的键(这里使用字体、大小和颜色来作为键)。在 get_attributes 方法中,我们首先检查给定属性的 TextAttributes 对象是否已经存在,如果存在则返回该对象,否则我们创建一个新的 TextAttributes 对象,将其添加到 HashMap 中,并返回新对象。

最后,我们在 main 函数中创建并使用 TextAttributesFactory 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut attributes_factory = TextAttributesFactory::new();

let font = "Arial";
let font_size = 12;
let color = "blue";

let text_attributes_1 = attributes_factory.get_attributes(font, font_size, color);
text_attributes_1.draw("Hello, World!");

let text_attributes_2 = attributes_factory.get_attributes(font, font_size, color);
text_attributes_2.draw("Rust is awesome!");
}

输出结果如下:

1
2
Drawing text 'Hello, World!' with font 'Arial' (size 12) and color 'blue'
Drawing text 'Rust is awesome!' with font 'Arial' (size 12) and color 'blue'

行为型模式

责任链模式(Chain of Responsibility Pattern)

责任链模式是一种行为型设计模式,它的主要目标是将一个请求从一系列对象中传递,直到有一个对象能够处理它为止。在 Rust 中,我们可以使用一个链表来实现责任链模式。

在本例中,我们将实现一个类似于购物车的程序,并使用责任链模式来处理购物车中的商品。

首先,定义一个基础结构体 CartItem,它包含商品的名称和价格:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct CartItem {
name: String,
price: f64,
}

impl CartItem {
fn new(name: &str, price: f64) -> Self {
Self {
name: name.to_owned(),
price,
}
}
}

然后,我们定义一个处理请求的 trait Handler,它有一个 handle 方法,用于处理请求,并支持链的继续:

1
2
3
4
trait Handler {
fn handle(&self, cart_item: &CartItem) -> Option<f64>;
fn set_next(&mut self, next: Box<dyn Handler>);
}

handle 方法中,我们向下传递请求,如果当前处理程序无法处理请求,则将请求传递给下一个处理程序。在 set_next 方法中,我们保存下一个处理程序的引用。

接下来,我们定义几个实现 Handler 的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
struct BaseDiscountHandler {
next: Option<Box<dyn Handler>>,
}

impl BaseDiscountHandler {
fn new() -> Self {
Self {
next: None,
}
}
}

impl Handler for BaseDiscountHandler {
fn handle(&self, cart_item: &CartItem) -> Option<f64> {
Some(cart_item.price)
}

fn set_next(&mut self, next: Box<dyn Handler>) {
self.next = Some(next);
}
}

struct TenPercentDiscountHandler {
next: Option<Box<dyn Handler>>,
}

impl TenPercentDiscountHandler {
fn new() -> Self {
Self {
next: None,
}
}
}

impl Handler for TenPercentDiscountHandler {
fn handle(&self, cart_item: &CartItem) -> Option<f64> {
let discount_price = cart_item.price * 0.1;
if discount_price < 10.0 {
self.next.as_ref().and_then(|handler| handler.handle(cart_item))
} else {
Some(cart_item.price - discount_price)
}
}

fn set_next(&mut self, next: Box<dyn Handler>) {
self.next = Some(next);
}
}

struct TwentyPercentDiscountHandler {
next: Option<Box<dyn Handler>>,
}

impl TwentyPercentDiscountHandler {
fn new() -> Self {
Self {
next: None,
}
}
}

impl Handler for TwentyPercentDiscountHandler {
fn handle(&self, cart_item: &CartItem) -> Option<f64> {
let discount_price = cart_item.price * 0.2;
if discount_price < 20.0 {
self.next.as_ref().and_then(|handler| handler.handle(cart_item))
} else {
Some(cart_item.price - discount_price)
}
}

fn set_next(&mut self, next: Box<dyn Handler>) {
self.next = Some(next);
}
}

在这些结构体中,我们逐步实现了三种不同的处理程序,分别为:基础价格处理程序、十分之一折扣处理程序和二十分之一折扣处理程序。每个处理程序都可以选择继续向下传递请求或返回计算出的价格。

最后,我们在 main 函数中使用责任链来处理购物车中的商品:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let mut base_discount_handler = BaseDiscountHandler::new();
let mut ten_percent_discount_handler = TenPercentDiscountHandler::new();
let mut twenty_percent_discount_handler = TwentyPercentDiscountHandler::new();

twenty_percent_discount_handler.set_next(Box::new(ten_percent_discount_handler));
ten_percent_discount_handler.set_next(Box::new(base_discount_handler));

let cart_item_1 = CartItem::new("Macbook Pro", 1500.0);
let cart_item_2 = CartItem::new("iPhone 12", 800.0);

let price_1 = twenty_percent_discount_handler.handle(&cart_item_1);
let price_2 = twenty_percent_discount_handler.handle(&cart_item_2);

println!("Price for {} is {}", cart_item_1.name, price_1.unwrap());
println!("Price for {} is {}", cart_item_2.name, price_2.unwrap());
}

输出结果如下:

1
2
Price for Macbook Pro is 1200
Price for iPhone 12 is 720

命令模式(Command Pattern)

命令模式是一种行为型设计模式,它的主要目的是在请求发送者和接收者之间插入一个命令对象,从而将请求封装成一个对象,使得请求的发送者不需要知道请求的接收者,也不需要知道请求如何被处理。

在 Rust 中,我们可以使用函数指针或闭包来实现命令模式。在这个例子中,我们将定义一个 Editor 结构体,它包含以下方法:

  • add_text:将一个字符串添加到编辑器中。
  • delete_text:删除编辑器中一个指定位置的字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Editor {
text: String,
}

impl Editor {
fn new() -> Self {
Self { text: String::new() }
}

fn add_text(&mut self, text: String) {
self.text.push_str(&text);
println!("Text added: {}", self.text);
}

fn delete_text(&mut self, position: usize) {
let text_before = self.text[..position].to_owned();
let text_after = self.text[position + 1..].to_owned();
self.text = format!("{}{}", text_before, text_after);
println!("Text deleted: {}", self.text);
}
}

接下来,我们定义一个 Command trait,它包含一个 execute 方法,用于执行命令操作:

1
2
3
trait Command {
fn execute(&mut self);
}

execute 方法中,我们将执行它们所代表的命令。

接下来,我们将定义一些实现 Command 的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct AddTextCommand<'a> {
editor: &'a mut Editor,
text: String,
}

impl<'a> AddTextCommand<'a> {
fn new(editor: &'a mut Editor, text: String) -> Self {
Self { editor, text }
}
}

impl<'a> Command for AddTextCommand<'a> {
fn execute(&mut self) {
self.editor.add_text(self.text.clone());
}
}

struct DeleteTextCommand<'a> {
editor: &'a mut Editor,
position: usize,
}

impl<'a> DeleteTextCommand<'a> {
fn new(editor: &'a mut Editor, position: usize) -> Self {
Self { editor, position }
}
}

impl<'a> Command for DeleteTextCommand<'a> {
fn execute(&mut self) {
self.editor.delete_text(self.position.clone());
}
}

在这些结构体中,我们具体实现了 Commandexecute 方法,以执行添加文本或删除文本的操作。

最后,我们定义一个 Invoker 结构体,用于执行命令操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Invoker {
history: Vec<Box<dyn Command>>,
}

impl Invoker {
fn new() -> Self {
Self { history: vec![] }
}

fn execute(&mut self, command: Box<dyn Command>) {
command.execute();
self.history.push(command);
}

fn undo(&mut self) {
if let Some(command) = self.history.pop() {
println!("Undoing last command...");
let mut command = *command;
command.execute();
} else {
println!("No more commands to undo.");
}
}
}

execute 方法中,我们将执行一个新的命令并将其添加到历史记录中。在 undo 方法中,我们将撤销最后一个命令。

最后,我们在 main 函数中使用命令模式来操作编辑器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
let mut editor = Editor::new();
let mut invoker = Invoker::new();

let add_command = AddTextCommand::new(&mut editor, String::from("Hello World!"));
let delete_command = DeleteTextCommand::new(&mut editor, 5);

invoker.execute(Box::new(add_command));
invoker.execute(Box::new(delete_command));

invoker.undo();
invoker.undo();
invoker.undo();
}

输出结果如下:

1
2
3
4
5
6
Text added: Hello World!
Text deleted: Hello!
Undoing last command...
Text added: Hello World!
Undoing last command...
No more commands to undo.

解释器模式(Interpreter Pattern)

解释器模式是一种行为型设计模式,它的主要目的是定义一个语言,以便可以解释执行特定的操作或动作。该模式包括环境、抽象表达式和具体表达式三个主要组成部分。

在 Rust 中,我们可以通过定义一个 Context 结构体来表示语言环境。Context 包含一些当前已经解释的表达式的状态信息。

1
2
3
4
5
struct Context {
input: String,
output: String,
position: usize,
}

接下来,我们定义一个 Expression trait,它包含一个 interpret 方法,用于解释执行表达式。在 interpret 方法中,我们将传递一个 Context 对象,并将该对象的状态根据表达式进行修改。

1
2
3
trait Expression {
fn interpret(&mut self, context: &mut Context);
}

在本例中,我们将定义两个不同的具体表达式—— AddExpressionSubExpression。他们分别表示加法和减法操作,并实现 Expression trait 中定义的 interpret 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct AddExpression {
left: Box<dyn Expression>,
right: Box<dyn Expression>,
}

impl AddExpression {
fn new(left: Box<dyn Expression>, right: Box<dyn Expression>) -> Self {
Self { left, right }
}
}

impl Expression for AddExpression {
fn interpret(&mut self, context: &mut Context) {
self.left.interpret(context);
self.right.interpret(context);
let left = context.output[..(context.output.len() - 2)].to_owned();
let right = context.output[(context.output.len() - 2)..].to_owned();
context.output = format!("{}{}", left, right.parse::<i32>().unwrap() + left.parse::<i32>().unwrap());
}
}

struct SubExpression {
left: Box<dyn Expression>,
right: Box<dyn Expression>,
}

impl SubExpression {
fn new(left: Box<dyn Expression>, right: Box<dyn Expression>) -> Self {
Self { left, right }
}
}

impl Expression for SubExpression {
fn interpret(&mut self, context: &mut Context) {
self.left.interpret(context);
self.right.interpret(context);
let left = context.output[..(context.output.len() - 2)].to_owned();
let right = context.output[(context.output.len() - 2)..].to_owned();
context.output = format!("{}{}", left, left.parse::<i32>().unwrap() - right.parse::<i32>().unwrap());
}
}

interpret 方法中,我们首先解释执行表达式的左侧和右侧,然后根据操作符执行加法或减法操作。

main 函数中,我们将创建一个新的 Context 对象,然后解释执行一些具体表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
fn main() {
let mut context = Context {
input: String::from("10 + 5 - 3 - 1 + 7"),
output: String::new(),
position: 0,
};

let mut expressions: Vec<Box<dyn Expression>> = vec![];

while context.position < context.input.len() {
if context.input.chars().nth(context.position).unwrap().is_numeric() {
expressions.push(Box::new(TerminalExpression::new()));
} else {
match context.input.chars().nth(context.position).unwrap() {
'+' => expressions.push(Box::new(AddExpression::new(
expressions.pop().unwrap(),
Box::new(TerminalExpression::new()),
))),
'-' => expressions.push(Box::new(SubExpression::new(
expressions.pop().unwrap(),
Box::new(TerminalExpression::new()),
))),
_ => (),
}
}
context.position += 1;
}

for expression in &mut expressions {
expression.interpret(&mut context);
}

println!("{} = {}", context.input, context.output);
}

在这里,我们创建一个 TerminalExpression,它表示具体表达式中的数字(例如, 105)。每当解释器遇到一个数字时,它将创建一个 TerminalExpression 对象,并将其添加到 expressions 向量中。

当它遇到一个操作符时,它将从 expressions 向量中弹出最后一个表达式,并将其与新的 AddExpressionSubExpression 结合。然后,该新的表达式被添加回表达式向量中,并用于下一个操作符的处理。

程序的输出如下:

1
10 + 5 - 3 - 1 + 7 = 18

由此可见,解释器模式成功地解释和执行了包含加法和减法的表达式。

迭代器模式(Iterator Pattern)

迭代器模式是一种行为型设计模式,用于提供一种遍历聚合对象元素的方法。它可以提供一个统一的接口,使得我们可以迭代不同种类聚合对象的元素。

在 Rust 中,迭代器由标准库提供,它们提供了一个共同的接口,使得可以轻松地迭代任何实现了该接口的集合类型。这个接口被称为 Iterator trait。

1
2
3
4
5
6
7
pub trait Iterator {
type Item;

fn next(&mut self) -> Option<Self::Item>;

// 其他默认方法,如 filter、map、enumerate 等
}

该 trait 包含一个 next 方法,它返回可迭代序列的下一个元素。每次调用 next,迭代器都会更新其内部状态来指向下一个元素,当该序列已经全部迭代完成时,next 将返回 None

实现自己的迭代器模式可以非常简单。我们只需要创建一个含有 next 方法的结构体,并在调用 next 方法时返回不同的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct MyIterator {
data: Vec<i32>,
position: usize,
}

impl MyIterator {
fn new(data: Vec<i32>) -> Self {
Self { data, position: 0 }
}
}

impl Iterator for MyIterator {
type Item = i32;

fn next(&mut self) -> Option<Self::Item> {
if self.position >= self.data.len() {
None
} else {
let result = Some(self.data[self.position]);
self.position += 1;
result
}
}
}

MyIterator 中,我们使用一个向量来存储待遍历的元素,并且使用 position 来追踪当前遍历到的位置。在每次调用 next 方法时,我们返回向量中对应的元素,并将 position 加 1。

现在,我们可以在 main 函数中创建一个 MyIterator 并迭代其中的元素。

1
2
3
4
5
6
7
fn main() {
let data = vec![1, 2, 3, 4, 5];
let mut iterator = MyIterator::new(data);
while let Some(val) = iterator.next() {
println!("{}", val);
}
}

输出结果如下:

1
2
3
4
5
1
2
3
4
5

中介者模式(Mediator Pattern)

中介者模式是一种行为型设计模式,它使得对象之间不直接相互交互,而是通过一个中介者对象来实现相互通信和协作。这种模式有助于降低系统的紧密耦合度,提高代码的可维护性和扩展性。

在 Rust 中,要实现中介者模式,我们可以先定义一个中介者结构体,并在其中实现相应的功能。这个中介者结构体需要知道所有参与协作的对象,以便将它们联系起来。

以下是一个实现中介者模式的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
struct Mediator {
a: Box<dyn Colleague>,
b: Box<dyn Colleague>,
}

trait Colleague {
fn set_mediator(&mut self, mediator: Box<dyn Mediator>);
fn receive(&self, sender: &str, message: &str);
fn send(&self, message: &str);
}

struct ConcreteColleagueA {
name: String,
mediator: Option<Box<Mediator>>,
}

impl ConcreteColleagueA {
pub fn new(name: String) -> Self {
Self {
name,
mediator: None,
}
}
}

impl Colleague for ConcreteColleagueA {
fn set_mediator(&mut self, mediator: Box<Mediator>) {
self.mediator = Some(mediator);
}

fn receive(&self, sender: &str, message: &str) {
println!("{} received from {}: {}", self.name, sender, message);
}

fn send(&self, message: &str) {
if let Some(ref m) = self.mediator {
m.b.receive(&self.name, message);
}
}
}

struct ConcreteColleagueB {
name: String,
mediator: Option<Box<Mediator>>,
}

impl ConcreteColleagueB {
pub fn new(name: String) -> Self {
Self {
name,
mediator: None,
}
}
}

impl Colleague for ConcreteColleagueB {
fn set_mediator(&mut self, mediator: Box<Mediator>) {
self.mediator = Some(mediator);
}

fn receive(&self, sender: &str, message: &str) {
println!("{} received from {}: {}", self.name, sender, message);
}

fn send(&self, message: &str) {
if let Some(ref m) = self.mediator {
m.a.receive(&self.name, message);
}
}
}

在这个例子中,我们定义了一个中介者结构体 Mediator 和两个同事结构体 ConcreteColleagueAConcreteColleagueB。其中,中介者结构体持有两个同事结构体的引用,而同事结构体则通过实现 Colleague trait 来实现相互通信。

Colleague trait 包含了 set_mediatorreceivesend 三个方法,分别用于设置中介者、接收消息和发送消息。当一个同事结构体需要向另一个同事结构体发送消息时,它会将消息内容发送给中介者,由中介者将消息发送给另一个同事结构体。

我们可以在 main 函数中实例化这些结构体,并测试它们之间的相互通信。

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let c1 = Box::new(ConcreteColleagueA::new("A".to_string()));
let c2 = Box::new(ConcreteColleagueB::new("B".to_string()));
let m = Box::new(Mediator { a: c1.clone(), b: c2.clone() });

c1.set_mediator(m.clone());
c2.set_mediator(m.clone());

c1.send("Hello from A");
c2.send("Hello from B");
}

输出结果如下:

1
2
B received from A: Hello from A
A received from B: Hello from B

备忘录模式(Memento Pattern)

备忘录模式是一种行为型设计模式,它能够在不破坏对象封装性的前提下,保存和恢复对象的内部状态。这个模式的核心是将对象状态封装在一个备忘录(Memento)对象中,再通过一个负责人(Caretaker)对象来保存和管理这些备忘录对象,以便实现对象状态的撤销和重做等功能。

在 Rust 中,要实现备忘录模式,我们可以使用结构体和 trait 来保存对象状态和管理备忘录。以下是一个实现备忘录模式的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
struct Memento {
state: String,
}

impl Memento {
fn new(state: String) -> Self {
Self { state }
}

fn get_state(&self) -> String {
self.state.clone()
}
}

struct Originator {
state: String,
}

impl Originator {
fn new(state: String) -> Self {
Self { state }
}

fn create_memento(&self) -> Memento {
Memento::new(self.state.clone())
}

fn set_memento(&mut self, memento: Memento) {
self.state = memento.get_state();
}

fn get_state(&self) -> String {
self.state.clone()
}

fn set_state(&mut self, state: String) {
self.state = state;
}
}

struct Caretaker {
mementos: Vec<Memento>,
originator: Originator,
}

impl Caretaker {
fn new(originator: Originator) -> Self {
Self {
mementos: Vec::new(),
originator,
}
}

fn save_state(&mut self) {
let memento = self.originator.create_memento();
self.mementos.push(memento);
}

fn undo(&mut self) {
if let Some(memento) = self.mementos.pop() {
self.originator.set_memento(memento);
}
}

fn show_state(&self) {
println!("Current state: {}", self.originator.get_state());
}
}

在这个例子中,我们定义了三个结构体 MementoOriginatorCaretaker,分别表示备忘录、原始对象和负责人。

Memento 结构体用来保存对象状态。它包含一个 state 字段,这个字段用来保存对象状态的值,以及一个 get_state 方法,用来获取对象状态的值。

Originator 结构体是一个原始对象,它拥有一个状态字段 state,并且实现了三个方法:create_mementoset_mementoget_state。其中,create_memento 方法用来创建一个备忘录对象,set_memento 方法用来设置对象的状态,而 get_state 方法用来获取对象的状态值。

Caretaker 结构体是负责人,它用来保存和管理备忘录对象。它拥有一个 mementos 字段用来保存备忘录对象,以及一个 originator 字段,用来保存原始对象。Caretaker 结构体实现了三个方法:save_stateundoshow_state。其中,save_state 方法用来保存当前对象状态到备忘录中,undo 方法用来撤销对象的状态,而 show_state 方法用来展示对象的当前状态。

main 函数中,我们可以实例化这些结构体,并测试它们之间的相互作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let mut originator = Originator::new("State 1".to_string());
let mut caretaker = Caretaker::new(originator.clone());

caretaker.save_state();
originator.set_state("State 2".to_string());

caretaker.save_state();
originator.set_state("State 3".to_string());

caretaker.show_state();
caretaker.undo();
caretaker.show_state();
caretaker.undo();
caretaker.show_state();
}

输出结果如下:

1
2
3
Current state: State 3
Current state: State 2
Current state: State 1

观察者模式(Observer Pattern)

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象发生变化时,它会通知所有注册的观察者对象,让它们自动更新自己的状态。这个模式的核心在于主题对象和观察者对象之间的解耦,使得它们可以独立地进行修改和扩展。

在 Rust 中,要实现观察者模式,我们可以使用 trait 来定义主题对象和观察者对象,再通过一个管理器来管理这些对象。以下是一个实现观察者模式的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use std::cell::RefCell;
use std::rc::{Rc, Weak};

trait Observer {
fn update(&self, message: &str);
}

struct Subject {
observers: Vec<Weak<dyn Observer>>,
}

impl Subject {
fn new() -> Self {
Self {
observers: Vec::new(),
}
}

fn add_observer(&mut self, observer: Rc<dyn Observer>) {
self.observers.push(Rc::downgrade(&observer));
}

fn remove_observer(&mut self, observer: Rc<dyn Observer>) {
self.observers.retain(|obs| {
if let Some(o) = obs.upgrade() {
!Rc::ptr_eq(&o, &observer)
} else {
false
}
});
}

fn notify_observers(&self, message: &str) {
for observer in self.observers.iter() {
if let Some(observer) = observer.upgrade() {
observer.update(message);
}
}
}
}

struct EmailObserver {
inbox: RefCell<Vec<String>>,
}

impl EmailObserver {
fn new() -> Self {
Self {
inbox: RefCell::new(Vec::new()),
}
}

fn check_inbox(&self) {
println!("--- Inbox ---");
for email in self.inbox.borrow().iter() {
println!("{}", email);
}
println!("-------------");
}
}

impl Observer for EmailObserver {
fn update(&self, message: &str) {
self.inbox.borrow_mut().push(message.to_string());
println!("Received email: {}", message);
}
}

struct MessageObserver {}

impl Observer for MessageObserver {
fn update(&self, message: &str) {
println!("Received message: {}", message);
}
}

fn main() {
let mut subject = Subject::new();
let email_observer = Rc::new(EmailObserver::new());
let message_observer = Rc::new(MessageObserver {});

subject.add_observer(email_observer.clone());
subject.add_observer(message_observer.clone());

subject.notify_observers("Hello, world!");

email_observer.check_inbox();

subject.remove_observer(email_observer.clone());

subject.notify_observers("Goodbye, world!");
}

在这个例子中,我们定义了三个结构体 SubjectEmailObserverMessageObserver,分别表示主题对象和观察者对象。

Subject 结构体用来管理所有的观察者对象。它包含一个 observers 字段,用来保存所有的观察者对象。Subject 结构体实现了三个方法:add_observerremove_observernotify_observers。其中,add_observer 方法用来添加观察者对象,remove_observer 方法用来移除观察者对象,而 notify_observers 方法用来通知所有观察者对象。

EmailObserverMessageObserver 结构体分别表示邮件观察者和消息观察者。它们都实现了 Observer trait,这个 trait 定义了一个 update 方法,用来接收主题对象通知的消息。EmailObserver 结构体还包含一个 inbox 字段,用来保存所有接收到的消息。

main 函数中,我们实例化了一个主题对象 subject 和两个观察者对象 email_observermessage_observer,将它们添加到主题对象中,并向主题对象发送两个消息。接着,我们调用了 check_inbox 方法查看邮件观察者的收件箱,并移除了该观察者对象,然后再次向主题对象发送一个消息。

输出结果如下:

1
2
3
4
5
6
Received email: Hello, world!
Received message: Hello, world!
--- Inbox ---
Hello, world!
-------------
Received message: Goodbye, world!

访问者模式(Visitor Pattern)

访问者模式是一种行为型设计模式,它允许您定义一些新操作,而无需更改现有对象的类。

访问者模式中有两种角色需要实现:

  • 访问者:定义访问者对象接口,该接口的每个方法都代表对象结构中的一种元素
  • 被访问者:定义被访问者接口,该接口通常只包含一个接受访问者对象的方法

这种模式的关键在于“双重分发”技术。分别分派被访问者和访问者对象到各自的方法中,以实现多态调用。

在 Rust 中,访问者模式可以通过 Trait 来实现。以下是实现访问者模式的 Rust 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 定义被访问者接口
trait Visit {
fn accept(&self, visitor: &impl Visitor);
}

// 实现 Book 结构体
struct Book {
name: String,
price: f32,
}
impl Book {
// 初始化方法
pub fn new(name: &str, price: f32) -> Book {
Book {
name: name.to_string(),
price: price
}
}
}
// 实现访问者接口
impl Visit for Book {
fn accept(&self, visitor: &impl Visitor) {
visitor.visit_book(self);
}
}

// 实现 CD 结构体
struct CD {
name: String,
price: f32,
tracks: Vec<String>,
}
impl CD {
// 初始化方法
pub fn new(name: &str, price: f32, tracks: Vec<String>) -> CD {
CD {
name: name.to_string(),
price: price,
tracks: tracks,
}
}
}
// 实现访问者接口
impl Visit for CD {
fn accept(&self, visitor: &impl Visitor) {
visitor.visit_cd(self);
}
}

// 定义访问者接口
trait Visitor {
fn visit_book(&self, book: &Book);
fn visit_cd(&self, cd: &CD);
}

// 实现实际的访问者
struct PriceVisitor {
total: f32
}
impl PriceVisitor {
fn new() -> PriceVisitor {
PriceVisitor {total: 0.0}
}
}
impl Visitor for PriceVisitor {
// 访问 Book
fn visit_book(&self, book: &Book) {
println!("Book: {} - {}", book.name, book.price);
self.total += book.price;
}
// 访问 CD
fn visit_cd(&self, cd: &CD) {
println!("CD: {} ({} tracks) - {}", cd.name, cd.tracks.len(), cd.price);
self.total += cd.price;
}
}

fn main() {
let mut visitor = PriceVisitor::new();
let book1 = Book::new("The Art of Computer Programming", 58.67);
let cd1 = CD::new("Queen - Greatest Hits 1", 8.99, vec![
String::from("Bohemian Rhapsody"),
String::from("Another One Bites the Dust"),
String::from("We Will Rock You"),
String::from("We Are the Champions"),
]);

let books_and_cds: Vec<&dyn Visit> = vec![&book1, &cd1];
for i in books_and_cds {
i.accept(&visitor);
}

println!("Total Price: {}", visitor.total);
}

在上面的示例中,我们有一个 Visit 工厂 trait 和两个具体的实现 BookCDVisit 工厂 trait 包含一个 accept 方法,该方法用于接受访问者。BookCD 都实现了 Visit 工厂 trait 以接受访问者。

然后,我们定义了一个 Visitor trait。该 trait 包含两个方法:visit_bookvisit_cd

我们也实现了一个实际的 Visiter,PriceVisitorPriceVisitor 包含一个 total 字段,表示所有被访问对象的总价值。PriceVisitor 实现了 Visitor trait 来实际访问 BookCD 实例。

main 函数中,我们创建了一个 PriceVisitor 实例,同时创建了一个 Book 和一个 CD 实例。然后将这两个实例添加到一个向量中,并分别使用 accept 函数来访问 visitor 对象。在访问完所有的对象之后,我们输出了 visitortotal 字段。

输出结果如下:

1
2
3
Book: The Art of Computer Programming - 58.67
CD: Queen - Greatest Hits 1 (4 tracks) - 8.99
Total Price: 67.66

状态模式(State Pattern)

状态模式是一种行为型设计模式,它允许您在对象内部状态改变时,将其行为改变为不同的行为。通过将这些行为委托给表示不同状态的对象,状态模式可以消除庞大而且难以理解的多分支条件语句(例如,if-else 和 switch-case 语句)。

在 Rust 中,状态模式可以通过结构体和 Trait 来实现。以下是实现状态模式的 Rust 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 定义状态 trait
trait State {
fn write_name(&self, name: &str) -> String;
}

// 实现 State trait 的实体类:LowerCase
struct LowerCase;
impl State for LowerCase {
fn write_name(&self, name: &str) -> String {
name.to_lowercase()
}
}

// 实现 State trait 的实体类:UpperCase
struct UpperCase;
impl State for UpperCase {
fn write_name(&self, name: &str) -> String {
name.to_uppercase()
}
}

// 实现 Context 结构体
struct Context {
state: Box<dyn State>,
}
impl Context {
// 初始化方法
fn new(state: Box<dyn State>) -> Context {
Context {state}
}
// 状态改变方法
fn set_state(&mut self, state: Box<dyn State>) {
self.state = state;
}
// 输出名字
fn write_name(&self, name: &str) -> String {
self.state.write_name(name)
}
}

fn main() {
let mut context = Context::new(Box::new(LowerCase));
let name1 = context.write_name("Jane");
println!("Name 1: {}", name1);

context.set_state(Box::new(UpperCase));
let name2 = context.write_name("John");
println!("Name 2: {}", name2);
}

在上面的示例中,我们使用了 trait 和结构体来实现状态模式。首先,我们定义了一个 State trait,该 trait 包含一个 write_name 方法来输出名字。然后,我们实现了两个结构体 LowerCaseUpperCase 分别代表不同的状态。这两个结构体实现了 State trait,并分别采用不同的大小写方式来输出名字。

接着,我们实现了 Context 结构体,该结构体拥有一个目前状态的 Box 指针。它还提供了一个 set_state 方法来改变状态,并提供了一个 write_name 方法来输出名字。

main 函数中,我们首先创建了一个 Context 实例,并给其赋予了 LowerCase 状态。然后使用 write_name 方法输出 Jane 的名字,并将结果存储在 name1 中。接着我们将状态改为 UpperCase,然后再次使用 write_name 方法输出 John 的名字,并将结果存储在 name2 中。

输出结果如下:

1
2
Name 1: jane
Name 2: JOHN

以下是生成的 UML 图:

策略模式(Strategy Pattern)

策略模式是一种行为型设计模式,它允许您可以根据需要在运行时切换算法或策略。通过将这些不同的算法或策略封装在单独的类中,并使它们可以互换,减少了庞大且重复的 if-else 语句,使系统更具灵活性和可拓展性。

在 Rust 中,策略模式可以通过 Trait 和结构体来实现。以下是实现策略模式的 Rust 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 定义策略 Trait
trait SortStrategy {
fn sort(&self, data: &mut Vec<i32>);
}

// 实现 SortStrategy Trait 的结构体:BubbleSort
struct BubbleSort;
impl SortStrategy for BubbleSort {
fn sort(&self, data: &mut Vec<i32>) {
let n = data.len();
for i in 0..n {
for j in (i+1..n).rev() {
if data[j] < data[j-1] {
data.swap(j, j-1);
}
}
}
}
}

// 实现 SortStrategy Trait 的结构体:QuickSort
struct QuickSort;
impl SortStrategy for QuickSort {
fn sort(&self, data: &mut Vec<i32>) {
sort_recursive(data, 0, data.len() - 1);
}
}

// 快排的递归函数
fn sort_recursive(data: &mut Vec<i32>, left: usize, right: usize) {
if left < right {
let pivot_idx = partition(data, left, right);
if pivot_idx > 0 {
sort_recursive(data, left, pivot_idx - 1);
}
sort_recursive(data, pivot_idx + 1, right);
}
}

// 快排的分区函数
fn partition(data: &mut Vec<i32>, left: usize, right: usize) -> usize {
let pivot = data[left];
let mut i = left;
let mut j = right;

loop {
while data[i] < pivot {
i += 1;
}
while data[j] > pivot {
j -= 1;
}
if i >= j {
return j;
}
data.swap(i, j);
i += 1;
j -= 1;
}
}

// 使用委托的方式实现 Context 结构体
struct Sorter {
strategy: Box<dyn SortStrategy>,
}
impl Sorter {
fn new(strategy: Box<dyn SortStrategy>) -> Sorter {
Sorter { strategy }
}
fn sort(&self, data: &mut Vec<i32>) {
self.strategy.sort(data);
}
}

fn main() {
let mut data1 = vec![3, 7, 5, 2, 9];
let sorter1 = Sorter::new(Box::new(BubbleSort));
sorter1.sort(&mut data1);
println!("BubbleSorted data1: {:?}", data1);

let mut data2 = vec![3, 7, 5, 2, 9];
let sorter2 = Sorter::new(Box::new(QuickSort));
sorter2.sort(&mut data2);
println!("QuickSorted data2: {:?}", data2);
}

在上面的示例中,我们使用 Trait 和结构体来实现策略模式。首先,我们定义了一个 SortStrategy trait,该 trait 包含一个 sort 方法,用于对数据进行排序。然后,我们实现了两个结构体 BubbleSortQuickSort 分别表示不同的排序策略。它们都实现了 SortStrategy trait,并分别使用冒泡排序和快速排序来排序数据。

接着,我们实现了 Sorter 结构体,该结构体拥有一个目前排序策略的 Box<dyn SortStrategy> 指针。它还提供了一个 sort 方法来对数据进行排序,并将任务委托给当前策略对象完成。

main 函数中,我们分别创建了两个 Sorter 实例,分别采用不同的排序策略(冒泡排序和快速排序)对相同的数据进行排序,并输出排序结果。

输出结果如下:

1
2
BubbleSorted data1: [2, 3, 5, 7, 9]
QuickSorted data2: [2, 3, 5, 7, 9]

模板方法模式(Template Method Pattern)

模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,而将一些步骤延迟到子类中实现。它允许您通过实现或覆盖子类的方法来改变算法的某些具体行为,但可以保持算法的框架不变。模板方法模式常用于设计大型的框架系统,在框架的基础上,开发者可以对其进行扩展与定制。

在 Rust 中,模板方法模式可以通过 Trait 和结构体来实现。以下是实现模板方法模式的 Rust 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义骨架 Trait
trait Algorithm {
fn initialize(&self) {
println!("Algorithm: initialize");
}
fn sorting(&self);
fn display_result(&self) {
println!("Algorithm: display result");
}
fn execute(&self) {
self.initialize();
self.sorting();
self.display_result();
}
}

// 实现骨架 Trait 的结构体:BubbleSort
struct BubbleSort;
impl Algorithm for BubbleSort {
fn sorting(&self) {
println!("BubbleSort: sorting");
}
}

// 实现骨架 Trait 的结构体:QuickSort
struct QuickSort;
impl Algorithm for QuickSort {
fn sorting(&self) {
println!("QuickSort: sorting");
}
}

fn main() {
let bubble_sort = BubbleSort;
bubble_sort.execute();

let quick_sort = QuickSort;
quick_sort.execute();
}

在上面的示例中,我们使用 Trait 和结构体来实现模板方法模式。首先,我们定义了一个 Algorithm trait,该 trait 包含了算法骨架的三个步骤:initializesortingdisplay_result。其中,initialize 方法用于初始化算法,sorting 方法用于具体排序操作,而 display_result 方法用于展示排序结果。我们还提供了一个 execute 方法,用于运行算法骨架。它按照事先定义好的执行顺序来调用三个步骤。

然后,我们分别实现了两个结构体 BubbleSortQuickSort,它们都实现了 Algorithm trait,并分别提供具体的排序算法。

main 函数中,我们分别创建了两个实例 bubble_sortquick_sort,并使用 execute 方法按照算法骨架来执行特定的排序算法。

输出结果如下:

1
2
3
4
5
6
Algorithm: initialize
BubbleSort: sorting
Algorithm: display result
Algorithm: initialize
QuickSort: sorting
Algorithm: display result