理想情況下,這些方法不應(yīng)該列出,除非密碼管理器已解鎖。

當(dāng)前解決方案的問題是它使用運(yùn)行時(shí)檢查,所以在調(diào)用list_passwords方法時(shí)不會(huì)給我們?nèi)魏尉幾g時(shí)錯(cuò)誤,我們只會(huì)在運(yùn)行時(shí)注意到問題以解決這些問題。下面讓我們探索一個(gè)編譯時(shí)檢查的解決方案。

解決方案二

該方案不是只使用一個(gè)密碼管理器結(jié)構(gòu)體,我們使用兩個(gè)結(jié)構(gòu)體,一個(gè)鎖定的密碼管理器結(jié)構(gòu)體和一個(gè)解鎖的密碼管理器結(jié)構(gòu)體:

struct LockedPasswordManager {
    master_pass: String,
    passwords: HashMap<String, String>,
}

struct UnlockedPasswordManager {
    master_pass: String,
    passwords: HashMap<String, String>,
}

那么我們就可以定義兩個(gè)單獨(dú)的實(shí)現(xiàn)塊:

impl LockedPasswordManager {
    pub fn new(master_pass: String) -> Self {
        LockedPasswordManager {
            master_pass,
            passwords: Default::default(),
        }
    }

    // 解鎖密碼管理器
    pub fn unlock(&self, master_pass: String) -> UnlockedPasswordManager {
        UnlockedPasswordManager { 
            master_pass: self.master_pass.clone(), 
            passwords: self.passwords.clone(), 
        }
    }

    // 獲取密碼管理器的加密算法
    pub fn encryption(&self) -> String {
        todo!()
    }

    // 獲取密碼管理器的版本信息
    pub fn version(&self) -> String {
        todo!()
    }
}

在LockedPasswordManager的實(shí)現(xiàn)塊中,包含一個(gè)構(gòu)造函數(shù),一個(gè)返回UnlockedPasswordManager結(jié)構(gòu)體的unlock方法,它還包含encryption方法和version方法。同時(shí),我們將lock方法、list_passwords方法和add_password方法移動(dòng)到了UnlockedPasswordManager實(shí)現(xiàn)塊中:

impl UnlockedPasswordManager {
    // 對(duì)密碼管理器加鎖
    pub fn lock(&self) -> LockedPasswordManager {
        LockedPasswordManager { 
            master_pass: self.master_pass.clone(), 
            passwords: self.passwords.clone()
        }
    }

    // 列出所有密碼
    pub fn list_passwords(&self) -> &HashMap<String, String> {
        todo!()
    }

    // 向密碼管理器添加密碼
    pub fn add_password(&mut self, name: String, password: String) {
        todo!()
    }

    // 獲取密碼管理器的加密算法
    pub fn encryption(&self) -> String {
        todo!()
    }

    // 獲取密碼管理器的版本信息
    pub fn version(&self) -> String {
        todo!()
    }
}

lock方法返回LockedPasswordManager結(jié)構(gòu)體,UnlockedPasswordManager還必須實(shí)現(xiàn)encryption和version方法,因?yàn)檫@些方法是通用的。

現(xiàn)在我們的API已經(jīng)更新了,讓我們看看如何在Main中使用它:

首先,我們修改PasswordManager為L(zhǎng)ockedPasswordManager,這將自動(dòng)給出編譯時(shí)錯(cuò)誤,因?yàn)閘ist_passwords方法和lock方法在LockedPasswordManager上不可用。這很好,因?yàn)橛脩糁荒茉L問在鎖定狀態(tài)下有意義的方法。修改main函數(shù)的代碼,如下:

fn main() {
    let mut manager = LockedPasswordManager::new("password123".to_owned());
    let manager =  manager.unlock("password123".to_owned());
    manager.list_passwords(); 
    manager.lock();
}

很好,現(xiàn)在我們可以防止API的用戶在編譯時(shí)誤用它了。但由于幾個(gè)原因,這種解決方案仍然不理想。

注意,在這兩個(gè)結(jié)構(gòu)體中有相當(dāng)多的重復(fù)代碼,包含相同的字段。這兩個(gè)結(jié)構(gòu)體也必須實(shí)現(xiàn)兩個(gè)狀態(tài)之間共同的功能,encryption方法和version方法。

我們希望保留編譯時(shí)檢查,但不需要所有這些重復(fù)的代碼。下面我們使用泛型和零大小類型來實(shí)現(xiàn)這一點(diǎn)。

解決方案三

我們重新定義結(jié)構(gòu)體:

use std::{collections::HashMap, marker::PhantomData};

struct Locked;
struct Unlocked;

struct PasswordManager<State = Locked> {
    master_pass: String,
    passwords: HashMap<String, String>,
    state: PhantomData<State>,
}

首先,我們回到了使用一個(gè)名為PasswordManager的結(jié)構(gòu)體,在結(jié)構(gòu)體中添加了一個(gè)名為State的新字段,類型為PhantomData。

同時(shí)還創(chuàng)建了兩個(gè)單元結(jié)構(gòu)體Locked和Unlocked,來表示鎖定和解鎖狀態(tài)。

接下來,我們還向PasswordManager結(jié)構(gòu)體添加一個(gè)泛型參數(shù)State,并將其默認(rèn)值設(shè)置為L(zhǎng)ocked。

添加泛型形參會(huì)導(dǎo)致一個(gè)問題,我們必須在結(jié)構(gòu)體的某個(gè)地方使用泛型形參,問題是我們并不關(guān)心這個(gè)泛型參數(shù),我們只使用它來創(chuàng)建不同的類型。

這就是PhantomData的來源,PhantomData是一種零大小類型,只是用于標(biāo)記。在編譯時(shí),這個(gè)字段實(shí)際上會(huì)被優(yōu)化掉,這就是為什么PhantomData被稱為零大小類型,因?yàn)樗徽加每臻g。

我們?cè)趯?shí)例化PasswordManager時(shí),必須將這個(gè)泛型形參替換為一個(gè)具體類型,默認(rèn)為L(zhǎng)ocked結(jié)構(gòu)體,也可能是Unlocked結(jié)構(gòu)體。

這么做這是有益的,因?yàn)殒i定的密碼管理器不等于解鎖的密碼管理器,這是兩種不同的類型,這意味著我們可以在每種類型上實(shí)現(xiàn)不同的方法。現(xiàn)在我們已經(jīng)重新定義了PasswordManager,讓我們來修改實(shí)現(xiàn)塊:

impl PasswordManager<Locked> {
    // 解鎖密碼管理器
    pub fn unlock(&self, master_pass: String) -> PasswordManager<Unlocked> {
        PasswordManager {
            master_pass: self.master_pass.clone(),
            passwords: self.passwords.clone(),
            state: PhantomData::<Unlocked>,
        }
    }
}

impl PasswordManager<Unlocked> {
    // 對(duì)密碼管理器加鎖
    pub fn lock(&self) -> PasswordManager<Locked> {
        PasswordManager {
            master_pass: self.master_pass.clone(),
            passwords: self.passwords.clone(),
            state: PhantomData::<Locked>,
        }
    }

    // 列出所有密碼
    pub fn list_passwords(&self) -> &HashMap<String, String> {
        &self.passwords
    }

    // 向密碼管理器添加密碼
    pub fn add_password(&mut self, name: String, password: String) {
        self.passwords.insert(name, password);
    }
}

impl<State> PasswordManager<State> {
    // 獲取密碼管理器的加密算法
    pub fn encryption(&self) -> String {
        todo!()
    }

    // 獲取密碼管理器的版本信息
    pub fn version(&self) -> String {
        todo!()
    }
}

impl PasswordManager {
    pub fn new(master_pass: String) -> Self {
        PasswordManager {
            master_pass,
            passwords: Default::default(),
            state: Default::default(),
        }
    }
}

我們已經(jīng)完成了密碼管理器的實(shí)現(xiàn),讓我們繼續(xù)修改Main函數(shù):

fn main() {
    let manager = PasswordManager::new("password123".to_owned());
    let manager = manager.unlock("password123".to_owned());
    manager.list_passwords();
    manager.lock();
}

恭喜,現(xiàn)在我們知道如何使用泛型、零大小類型及狀態(tài)模式在提高Rust api性能的同時(shí)還能防止API的使用者濫用API

文章轉(zhuǎn)自微信公眾號(hào)@coding到燈火闌珊

上一篇:

如何通過CD平臺(tái)如何將數(shù)據(jù)以API的方式同步到facebook

下一篇:

深入探索 Rust Salvo:從簡(jiǎn)單博客系統(tǒng)到完整 RESTful API 的實(shí)戰(zhàn)項(xiàng)目
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)