Rust语言之GoF设计模式:外观Facade模式


Facade(外观、总管)是一种结构设计模式,它为复杂的类、库或框架系统提供了一个简化的(但有限的)接口。

API其实是一种Facade模式的实现,在API背后隐藏了一个复杂的逻辑。
Facade模式类似于组合模式,都是将复杂或繁杂Complicated的细节隐藏起来,提供一个干净的外观门面,如同一个美女看起来很漂亮干净,但是家里东西却乱七八糟一样。

例如:wallet_facade.rs 提供了一个方法add_money_to_wallet,但是在其后隐藏了与账户、代码、钱包、通知和分类账进行交互。

use crate::{
    account::Account, ledger::Ledger, notification::Notification, security_code::SecurityCode,
    wallet::Wallet,
};

/// Facade hides a complex logic behind the API.
pub struct WalletFacade {
    account: Account,
    wallet: Wallet,
    code: SecurityCode,
    notification: Notification,
    ledger: Ledger,
}

impl WalletFacade {
    pub fn new(account_id: String, code: u32) -> Self {
        println!(
"Starting create account");

        let this = Self {
            account: Account::new(account_id),
            wallet: Wallet::new(),
            code: SecurityCode::new(code),
            notification: Notification,
            ledger: Ledger,
        };

        println!(
"Account created");
        this
    }

    pub fn add_money_to_wallet(
        &mut self,
        account_id: &String,
        security_code: u32,
        amount: u32,
    ) -> Result<(), String> {
        println!(
"Starting add money to wallet");
        self.account.check(account_id)?;
        self.code.check(security_code)?;
        self.wallet.credit_balance(amount);
        self.notification.send_wallet_credit_notification();
        self.ledger.make_entry(account_id,
"credit".into(), amount);
        Ok(())
    }

    pub fn deduct_money_from_wallet(
        &mut self,
        account_id: &String,
        security_code: u32,
        amount: u32,
    ) -> Result<(), String> {
        println!(
"Starting debit money from wallet");
        self.account.check(account_id)?;
        self.code.check(security_code)?;
        self.wallet.debit_balance(amount);
        self.notification.send_wallet_debit_notification();
        self.ledger.make_entry(account_id,
"debit".into(), amount);
        Ok(())
    }
}


Rust模块重导出
Rust语言中通过模块 Re-Export实现门面模式:
模块 Re-Export 是重导出功能。
比如,现在有如下模块层级:

#![allow(unused)]
fn main() {
src/
    - lib.rs
    - module/
        -- mod.rs
        -- submodule/
            --- mod.rs
}

Rust 能让最深处的那个模块 submodule 里定义的函数,使用重导出功能,变成整个库的「门面」接口。


#![allow(unused)]
fn main() {
// in module/submodule/mod.rs
pub fn goodbye(){}

// in lib.rs
pub use module::submodule::goodbye;

那么在使用这个库(假设叫 hello)的时候,只需要使用 hello::goodby就可以使用这个函数。
这种方式在 Rust 的世界大量使用。比如 标准库 很多接口是重导出了 核心库 的 API。

Rust条件编译
条件编译也是一种 门面模式。
比如在 TiKV 中,使用 条件编译 和 features 来支持多种内存分配器。


#![allow(unused)]
fn main() {
 #[cfg(all(unix, not(fuzzing), feature = "jemalloc"))]
 #[path =
"jemalloc.rs"]
 mod imp;

 #[cfg(all(unix, not(fuzzing), feature =
"tcmalloc"))]
 #[path =
"tcmalloc.rs"]
 mod imp;

 #[cfg(all(unix, not(fuzzing), feature =
"mimalloc"))]
 #[path =
"mimalloc.rs"]
 mod imp;

 #[cfg(not(all(
    unix,
    not(fuzzing),
    any(feature =
"jemalloc", feature = "tcmalloc", feature = "mimalloc")
 )))]
 #[path =
"system.rs"]
 mod imp;
}

实际上并不存在 imp 模块,通过不同的 cfg 判断,对应不同的 path,从而选择相应的模块:jemalloc.rs/tcmalloc.rs/mimalloc.rs/system.rs。而 imp 模块就是一个「门面」。

利用 类型 和 Trait实现门面
第三种方式,就是常规的 利用 类型 和 trait 来实现门面模型。
这种方式类似Java中实现统一接口,或统一API做法。