MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Rust面向对象设计模式的实现

2024-12-265.8k 阅读

Rust 中的面向对象基础

Rust 虽然不是传统意义上以类为中心的面向对象语言,但它通过结构体(struct)、特征(trait)和方法(method)提供了强大的面向对象编程能力。

结构体(Struct)

结构体是 Rust 中用于聚合不同类型数据的自定义数据类型。例如,我们定义一个表示用户的结构体:

struct User {
    username: String,
    age: u32,
}

这里 User 结构体包含两个字段,username 是字符串类型,age 是无符号 32 位整数类型。

特征(Trait)

特征定义了一组方法的签名,但不包含方法的实现。它类似于其他语言中的接口。例如,我们定义一个 DisplayInfo 特征,用于显示用户信息:

trait DisplayInfo {
    fn display(&self);
}

这里 display 方法接受一个 &self 参数,表示对结构体实例的不可变引用。

方法(Method)

方法是与结构体或枚举相关联的函数。我们可以为 User 结构体实现 DisplayInfo 特征中的 display 方法:

impl DisplayInfo for User {
    fn display(&self) {
        println!("Username: {}, Age: {}", self.username, self.age);
    }
}

然后在 main 函数中可以使用:

fn main() {
    let user = User {
        username: String::from("JohnDoe"),
        age: 30,
    };
    user.display();
}

上述代码展示了 Rust 如何通过结构体、特征和方法来实现类似面向对象的行为,如封装(将数据和操作封装在结构体和其方法中)和多态(通过特征实现)。

单例模式(Singleton Pattern)

单例模式确保一个类只有一个实例,并提供一个全局访问点。在 Rust 中,我们可以利用 lazy_static 库来实现单例模式。

引入依赖

首先在 Cargo.toml 文件中添加依赖:

[dependencies]
lazy_static = "1.4.0"

实现单例

use lazy_static::lazy_static;
use std::sync::Mutex;

struct Database {
    data: String,
}

impl Database {
    fn get_data(&self) -> &str {
        &self.data
    }
}

lazy_static! {
    static ref INSTANCE: Mutex<Database> = Mutex::new(Database {
        data: String::from("Initial data"),
    });
}

fn main() {
    let db1 = INSTANCE.lock().unwrap();
    let db2 = INSTANCE.lock().unwrap();
    assert_eq!(db1.get_data(), db2.get_data());
}

在上述代码中,lazy_static! 宏创建了一个全局静态变量 INSTANCE,它是一个 Mutex 包裹的 Database 实例。Mutex 用于线程安全,确保在多线程环境下只有一个线程可以访问单例实例。

工厂模式(Factory Pattern)

工厂模式提供了一种创建对象的方式,将对象的创建和使用分离。

简单工厂模式(Simple Factory Pattern)

假设我们有不同类型的图形,如圆形和矩形,我们可以使用简单工厂模式来创建它们。

首先定义图形的特征和具体图形结构体:

trait Shape {
    fn draw(&self);
}

struct Circle {
    radius: f64,
}

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

struct Rectangle {
    width: f64,
    height: f64,
}

impl Shape for Rectangle {
    fn draw(&self) {
        println!("Drawing a rectangle with width {} and height {}", self.width, self.height);
    }
}

然后定义简单工厂:

enum ShapeType {
    Circle,
    Rectangle,
}

struct ShapeFactory;

impl ShapeFactory {
    fn create_shape(shape_type: ShapeType) -> Box<dyn Shape> {
        match shape_type {
            ShapeType::Circle => Box::new(Circle { radius: 5.0 }),
            ShapeType::Rectangle => Box::new(Rectangle { width: 10.0, height: 5.0 }),
        }
    }
}

main 函数中使用:

fn main() {
    let circle = ShapeFactory::create_shape(ShapeType::Circle);
    circle.draw();
    let rectangle = ShapeFactory::create_shape(ShapeType::Rectangle);
    rectangle.draw();
}

这里 ShapeFactory 负责根据传入的 ShapeType 创建相应的图形实例,客户端只需要使用工厂创建的实例,而不需要关心具体的创建过程。

工厂方法模式(Factory Method Pattern)

工厂方法模式将对象的创建延迟到子类。我们对上述代码进行修改,引入一个抽象的工厂特征和具体的工厂结构体。

定义抽象工厂特征:

trait ShapeFactoryTrait {
    fn create_shape(&self) -> Box<dyn Shape>;
}

定义具体的工厂结构体:

struct CircleFactory;

impl ShapeFactoryTrait for CircleFactory {
    fn create_shape(&self) -> Box<dyn Shape> {
        Box::new(Circle { radius: 5.0 })
    }
}

struct RectangleFactory;

impl ShapeFactoryTrait for RectangleFactory {
    fn create_shape(&self) -> Box<dyn Shape> {
        Box::new(Rectangle { width: 10.0, height: 5.0 })
    }
}

main 函数中使用:

fn main() {
    let circle_factory = CircleFactory;
    let circle = circle_factory.create_shape();
    circle.draw();
    let rectangle_factory = RectangleFactory;
    let rectangle = rectangle_factory.create_shape();
    rectangle.draw();
}

在工厂方法模式中,每个具体的工厂负责创建一种特定类型的对象,客户端通过调用具体工厂的 create_shape 方法来获取对象,增加了代码的可扩展性和灵活性。

策略模式(Strategy Pattern)

策略模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。在 Rust 中,我们可以通过特征和结构体来实现策略模式。

假设我们有不同的排序策略,如冒泡排序和快速排序。

首先定义排序策略的特征:

trait SortStrategy {
    fn sort(&self, numbers: &mut [i32]);
}

然后实现冒泡排序策略:

struct BubbleSort;

impl SortStrategy for BubbleSort {
    fn sort(&self, numbers: &mut [i32]) {
        let len = numbers.len();
        for i in 0..len {
            for j in 0..len - i - 1 {
                if numbers[j] > numbers[j + 1] {
                    numbers.swap(j, j + 1);
                }
            }
        }
    }
}

接着实现快速排序策略:

struct QuickSort;

impl SortStrategy for QuickSort {
    fn sort(&self, numbers: &mut [i32]) {
        fn partition(numbers: &mut [i32], low: usize, high: usize) -> usize {
            let pivot = numbers[high];
            let mut i = low - 1;
            for j in low..high {
                if numbers[j] <= pivot {
                    i = i + 1;
                    numbers.swap(i, j);
                }
            }
            numbers.swap(i + 1, high);
            i + 1
        }

        fn quick_sort_helper(numbers: &mut [i32], low: usize, high: usize) {
            if low < high {
                let pi = partition(numbers, low, high);
                quick_sort_helper(numbers, low, pi - 1);
                quick_sort_helper(numbers, pi + 1, high);
            }
        }

        quick_sort_helper(numbers, 0, numbers.len() - 1);
    }
}

定义一个使用排序策略的结构体:

struct Sorter {
    strategy: Box<dyn SortStrategy>,
}

impl Sorter {
    fn new(strategy: Box<dyn SortStrategy>) -> Sorter {
        Sorter { strategy }
    }

    fn sort(&self, numbers: &mut [i32]) {
        self.strategy.sort(numbers);
    }
}

main 函数中使用:

fn main() {
    let mut numbers = vec![5, 4, 3, 2, 1];
    let sorter = Sorter::new(Box::new(BubbleSort));
    sorter.sort(&mut numbers);
    println!("Sorted by bubble sort: {:?}", numbers);

    let mut numbers = vec![5, 4, 3, 2, 1];
    let sorter = Sorter::new(Box::new(QuickSort));
    sorter.sort(&mut numbers);
    println!("Sorted by quick sort: {:?}", numbers);
}

上述代码中,Sorter 结构体持有一个排序策略的实例,通过传入不同的排序策略(BubbleSortQuickSort),可以使用不同的排序算法对数据进行排序,体现了策略模式的灵活性。

观察者模式(Observer Pattern)

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

在 Rust 中,我们可以使用 std::sync::{Arc, Mutex}std::collections::HashMap 来实现观察者模式。

首先定义主题和观察者的特征:

use std::sync::{Arc, Mutex};
use std::collections::HashMap;

trait Subject {
    fn attach(&self, observer: Arc<dyn Observer>);
    fn detach(&self, observer: Arc<dyn Observer>);
    fn notify(&self);
}

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

定义具体的主题结构体:

struct ConcreteSubject {
    observers: Mutex<HashMap<String, Arc<dyn Observer>>>,
    data: Mutex<String>,
}

impl Subject for ConcreteSubject {
    fn attach(&self, observer: Arc<dyn Observer>) {
        let mut observers = self.observers.lock().unwrap();
        let name = std::any::type_name::<dyn Observer>();
        observers.insert(String::from(name), observer);
    }

    fn detach(&self, observer: Arc<dyn Observer>) {
        let mut observers = self.observers.lock().unwrap();
        let name = std::any::type_name::<dyn Observer>();
        observers.remove(&String::from(name));
    }

    fn notify(&self) {
        let data = self.data.lock().unwrap().clone();
        let observers = self.observers.lock().unwrap();
        for (_, observer) in observers.iter() {
            observer.update(&data);
        }
    }

    fn set_data(&self, new_data: String) {
        let mut data = self.data.lock().unwrap();
        *data = new_data;
        self.notify();
    }
}

定义具体的观察者结构体:

struct ConcreteObserver {
    name: String,
}

impl Observer for ConcreteObserver {
    fn update(&self, data: &str) {
        println!("Observer {} received data: {}", self.name, data);
    }
}

main 函数中使用:

fn main() {
    let subject = Arc::new(ConcreteSubject {
        observers: Mutex::new(HashMap::new()),
        data: Mutex::new(String::from("Initial data")),
    });

    let observer1 = Arc::new(ConcreteObserver {
        name: String::from("Observer1"),
    });
    let observer2 = Arc::new(ConcreteObserver {
        name: String::from("Observer2"),
    });

    subject.attach(observer1.clone());
    subject.attach(observer2.clone());

    subject.set_data(String::from("New data"));

    subject.detach(observer1);
    subject.set_data(String::from("Another new data"));
}

在上述代码中,ConcreteSubject 是主题,它维护一个观察者的列表,并在数据更新时通知所有观察者。ConcreteObserver 是具体的观察者,实现了 Observer 特征的 update 方法,在接收到通知时更新自身状态。

装饰器模式(Decorator Pattern)

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。在 Rust 中,我们可以通过特征和结构体组合来实现装饰器模式。

假设我们有一个简单的 Component 特征和一个具体的 ConcreteComponent 结构体:

trait Component {
    fn operation(&self) -> String;
}

struct ConcreteComponent;

impl Component for ConcreteComponent {
    fn operation(&self) -> String {
        String::from("Base operation")
    }
}

然后定义装饰器特征和具体的装饰器结构体:

trait Decorator: Component {
    fn new(component: Box<dyn Component>) -> Self;
    fn component(&self) -> &Box<dyn Component>;
}

struct ConcreteDecoratorA {
    component: Box<dyn Component>,
}

impl Decorator for ConcreteDecoratorA {
    fn new(component: Box<dyn Component>) -> Self {
        ConcreteDecoratorA { component }
    }

    fn component(&self) -> &Box<dyn Component> {
        &self.component
    }
}

impl Component for ConcreteDecoratorA {
    fn operation(&self) -> String {
        format!("{} + Decorator A", self.component.operation())
    }
}

struct ConcreteDecoratorB {
    component: Box<dyn Component>,
}

impl Decorator for ConcreteDecoratorB {
    fn new(component: Box<dyn Component>) -> Self {
        ConcreteDecoratorB { component }
    }

    fn component(&self) -> &Box<dyn Component> {
        &self.component
    }
}

impl Component for ConcreteDecoratorB {
    fn operation(&self) -> String {
        format!("{} + Decorator B", self.component.operation())
    }
}

main 函数中使用:

fn main() {
    let component = Box::new(ConcreteComponent);
    let decorated_component_a = Box::new(ConcreteDecoratorA::new(component));
    let decorated_component_b = Box::new(ConcreteDecoratorB::new(decorated_component_a));

    println!("{}", decorated_component_b.operation());
}

上述代码中,ConcreteDecoratorAConcreteDecoratorB 是具体的装饰器,它们在 operation 方法中调用被装饰对象的 operation 方法,并添加额外的功能。通过这种方式,可以动态地为对象添加新的功能,而不需要修改对象的原有结构。

代理模式(Proxy Pattern)

代理模式为其他对象提供一种代理以控制对这个对象的访问。在 Rust 中,我们可以通过结构体和特征来实现代理模式。

假设我们有一个远程服务的接口和具体实现:

trait RemoteService {
    fn request(&self) -> String;
}

struct RealRemoteService;

impl RemoteService for RealRemoteService {
    fn request(&self) -> String {
        String::from("Real service request")
    }
}

然后定义代理结构体:

struct RemoteServiceProxy {
    real_service: Option<RealRemoteService>,
}

impl RemoteService for RemoteServiceProxy {
    fn request(&self) -> String {
        if self.real_service.is_none() {
            self.real_service = Some(RealRemoteService);
        }
        self.real_service.as_ref().unwrap().request()
    }
}

main 函数中使用:

fn main() {
    let proxy = RemoteServiceProxy { real_service: None };
    println!("{}", proxy.request());
}

在上述代码中,RemoteServiceProxy 作为 RealRemoteService 的代理。当调用 request 方法时,代理会在需要时创建真实服务的实例,并转发请求。这样可以在访问真实服务之前进行一些额外的操作,如权限检查、缓存等。

桥接模式(Bridge Pattern)

桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。在 Rust 中,我们可以通过特征和结构体组合来实现桥接模式。

假设我们有一个图形绘制的抽象和具体实现:

trait DrawAPI {
    fn draw_circle(&self, x: i32, y: i32, radius: i32);
}

struct DrawingAPI1;

impl DrawAPI for DrawingAPI1 {
    fn draw_circle(&self, x: i32, y: i32, radius: i32) {
        println!("API1 drawing a circle at ({}, {} with radius {}", x, y, radius);
    }
}

struct DrawingAPI2;

impl DrawAPI for DrawingAPI2 {
    fn draw_circle(&self, x: i32, y: i32, radius: i32) {
        println!("API2 drawing a circle at ({}, {} with radius {}", x, y, radius);
    }
}

struct Shape {
    draw_api: Box<dyn DrawAPI>,
    x: i32,
    y: i32,
    radius: i32,
}

impl Shape {
    fn new(draw_api: Box<dyn DrawAPI>, x: i32, y: i32, radius: i32) -> Self {
        Shape { draw_api, x, y, radius }
    }

    fn draw(&self) {
        self.draw_api.draw_circle(self.x, self.y, self.radius);
    }
}

main 函数中使用:

fn main() {
    let shape1 = Shape::new(Box::new(DrawingAPI1), 1, 2, 3);
    let shape2 = Shape::new(Box::new(DrawingAPI2), 4, 5, 6);

    shape1.draw();
    shape2.draw();
}

上述代码中,Shape 结构体持有一个 DrawAPI 特征对象,通过这种方式将图形的抽象(Shape)和具体的绘制实现(DrawingAPI1DrawingAPI2)分离,使得它们可以独立变化。

组合模式(Composite Pattern)

组合模式将对象组合成树形结构以表示“部分 - 整体”的层次结构。在 Rust 中,我们可以通过递归结构体和特征来实现组合模式。

假设我们有一个文件系统的例子,文件和文件夹都可以包含其他文件或文件夹:

trait FileSystemComponent {
    fn display(&self, depth: u32);
}

struct File {
    name: String,
    size: u32,
}

impl FileSystemComponent for File {
    fn display(&self, depth: u32) {
        let indent = "  ".repeat(depth as usize);
        println!("{}File: {} (Size: {})", indent, self.name, self.size);
    }
}

struct Directory {
    name: String,
    children: Vec<Box<dyn FileSystemComponent>>,
}

impl FileSystemComponent for Directory {
    fn display(&self, depth: u32) {
        let indent = "  ".repeat(depth as usize);
        println!("{}Directory: {}", indent, self.name);
        for child in self.children.iter() {
            child.display(depth + 1);
        }
    }

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

main 函数中使用:

fn main() {
    let mut root = Directory {
        name: String::from("Root"),
        children: Vec::new(),
    };

    let file1 = Box::new(File {
        name: String::from("file1.txt"),
        size: 1024,
    });
    let file2 = Box::new(File {
        name: String::from("file2.txt"),
        size: 2048,
    });

    let mut sub_dir = Directory {
        name: String::from("SubDir"),
        children: Vec::new(),
    };
    sub_dir.add(Box::new(File {
        name: String::from("sub_file.txt"),
        size: 512,
    }));

    root.add(file1);
    root.add(file2);
    root.add(Box::new(sub_dir));

    root.display(0);
}

上述代码中,Directory 结构体可以包含多个 FileSystemComponent,包括 File 和其他 Directory,形成了一个树形结构。display 方法递归地显示整个文件系统层次结构,体现了组合模式的特点。

享元模式(Flyweight Pattern)

享元模式运用共享技术有效地支持大量细粒度的对象。在 Rust 中,我们可以通过 Rc(引用计数)和 Weak(弱引用)来实现享元模式。

假设我们有一个文本渲染系统,其中字符是细粒度的对象:

use std::rc::Rc;
use std::weak::Weak;

struct Glyph {
    character: char,
    // 其他可能的共享属性
}

struct GlyphFactory {
    glyphs: std::collections::HashMap<char, Rc<Glyph>>,
}

impl GlyphFactory {
    fn new() -> Self {
        GlyphFactory {
            glyphs: std::collections::HashMap::new(),
        }
    }

    fn get_glyph(&mut self, character: char) -> Rc<Glyph> {
        if let Some(glyph) = self.glyphs.get(&character) {
            glyph.clone()
        } else {
            let new_glyph = Rc::new(Glyph { character });
            self.glyphs.insert(character, new_glyph.clone());
            new_glyph
        }
    }
}

struct TextLine {
    glyphs: Vec<Weak<Glyph>>,
}

impl TextLine {
    fn new(factory: &mut GlyphFactory, text: &str) -> Self {
        let mut glyphs = Vec::new();
        for c in text.chars() {
            let glyph = factory.get_glyph(c);
            glyphs.push(Rc::downgrade(&glyph));
        }
        TextLine { glyphs }
    }

    fn render(&self) {
        for glyph_weak in self.glyphs.iter() {
            if let Some(glyph) = glyph_weak.upgrade() {
                print!("{}", glyph.character);
            }
        }
        println!();
    }
}

main 函数中使用:

fn main() {
    let mut factory = GlyphFactory::new();
    let line1 = TextLine::new(&mut factory, "Hello");
    let line2 = TextLine::new(&mut factory, "World");

    line1.render();
    line2.render();
}

在上述代码中,GlyphFactory 负责创建和管理共享的 Glyph 对象。TextLine 持有对 Glyph 的弱引用,通过共享 Glyph 对象,减少了内存的使用,体现了享元模式的核心思想。

通过上述对各种设计模式在 Rust 中的实现,我们可以看到 Rust 虽然在面向对象编程方面有其独特的方式,但依然能够很好地实现常见的面向对象设计模式,为复杂软件系统的开发提供有力支持。