< 返回版块

Mike Tang 发表于 2020-02-13 02:56

Tags:rust,design pattern

最近发现一种 Rust 的设计模式,有点好玩,发出来大家探讨一下。示例如下:

use std::ops::Deref;

trait TraitFoo {
    fn foo(&self);
}

struct A;

impl TraitFoo for A {
    fn foo(&self) {
	println!("Huh, I'm foo!");
    }
}

struct B<T> {
    behavior: T
}

impl<T> B<T> {
    pub fn new(behavior: T) -> B<T> {
	B {
	    behavior
	}
    }
}

impl<T> Deref for B<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
	&self.behavior
    }
}


trait TraitBar {
    fn bar(&self);
}

struct C;

impl TraitBar for C {
    fn bar(&self) {
	println!("Huh, I'm bar!");
    }
}


fn main() {
    let a = A;
    let b = B::new(a);
    b.foo();

    let c = C;
    let b = B::new(c);
    b.bar();
}

运行结果:

Huh, I'm foo!
Huh, I'm bar!

大体意思就是,B 是一个类型,它接受一个泛型作为它的形参,然后“盗用”了它的实参的方法。

如果 B 实现为一个库,对外导出 B 类型,供别人使用。那么在上层用户来看,就可以很灵活地为这个导出类型的实例添加“额外”的方法。而这些方法,并不是使用通常的为“外部类型”实现“本地 trait”的技术来达到。而是通过将本地实现的类型的实例作为参数传入 B 的构造器来达到。感觉有点意思。

小编才疏学浅,不知道社区中以前有没有过对此种设计模式的讨论,或者其它语言中有没有对应的模式,是不是已命名?大家一起讨论。

评论区

写评论
Undefined01 2020-02-13 14:08

这个其实比较早的时候就有人发过类似的讨论,具体在哪忘记了。

大概就是因为 Rust 不支持 struct 的继承,如果需要添加方法需要用 struct A(B) 之类的二次包装并且重新暴露方法。因此有人提出使用 . 操作符的自动解引用(调用 deref 方法)来实现伪继承,进而复用代码。

但是其实 Rust 不支持继承也有自己的一番考虑。 Rust 的早期版本有实验过继承,后面因为不符合 Rust 的理念删除了。既然 Rust 本身就认为不应该使用继承,那最好还是别和语言作对硬生生的实现继承。使用 deref 实现也没有显式的继承关系,项目大了容易混乱。

这里有一些常用的设计模式(虽然不是官方的)。而deref 被列为了“反模式”,即不符合 Rust 理念的设计模式。

Ryan-Git 2020-02-13 13:58

这不就是智能指针吗

作者 Mike Tang 2020-02-13 13:32

明白了,谢谢。这也算一个模式吧,deref只是里面使用的技术。

linjio 2020-02-13 12:03

web::Json定义是

pub struct Json<T>(pub T);

因不太确定访问T,是否为e.0的形式 所以我用actix_web::web::Path举例,其原代码为:

pub struct Path<T> {
    inner: T,
}

impl<T> Path<T> {
    /// Deconstruct to an inner value
    pub fn into_inner(self) -> T {
        self.inner
    }
}

impl<T> AsRef<T> for Path<T> {
    fn as_ref(&self) -> &T {
        &self.inner
    }
}

impl<T> ops::Deref for Path<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.inner
    }
}

impl<T> ops::DerefMut for Path<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.inner
    }
}
// 忽略部分实现

使用例子如下:

use actix_web::{web, App, Error};
use serde_derive::Deserialize;

#[derive(Deserialize)]
struct Info {
    username: String,
}

/// extract `Info` from a path using serde
async fn index(info: web::Path<Info>) -> Result<String, Error> {
    Ok(format!("Welcome {}!", info.username))
}

fn main() {
    let app = App::new().service(
        web::resource("/{username}/index.html") // <- define path parameters
             .route(web::get().to(index)) // <- use handler with Path` extractor
    );
}

在函数index中,我们可以使用下面的方式访问struct Info中的username:

async fn index(info: web::Path<Info>) -> Result<String, Error> {
    Ok(format!("Welcome {}!", info.into_inner().username))
}

而例子中因实现Deref trait,我们可以直接使用访问:

async fn index(info: web::Path<Info>) -> Result<String, Error> {
    Ok(format!("Welcome {}!", info.username))
}

对比,少了一个into_inner的方法调用,个人认为和题主所表达的意思差不多,都跳过了中间步骤。

作者 Mike Tang 2020-02-13 11:33

类似在哪里,解释一下?

linjio 2020-02-13 11:13

actix-web中运用的类似设计,如下

use actix_web::{web, App};
use serde_derive::Deserialize;

#[derive(Deserialize)]
struct Info {
    username: String,
}

/// deserialize `Info` from request's body
async fn index(info: web::Json<Info>) -> String {
    format!("Welcome {}!", info.username)
}

fn main() {
    let app = App::new().service(
       web::resource("/index.html").route(
           web::post().to(index))
    );
}
作者 Mike Tang 2020-02-13 09:40

是的

zys864 2020-02-13 09:28

是不是 . 方法的时候自动deref了

1 共 8 条评论, 1 页