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

當前解決方案的問題是它使用運行時檢查,所以在調用list_passwords方法時不會給我們任何編譯時錯誤,我們只會在運行時注意到問題以解決這些問題。下面讓我們探索一個編譯時檢查的解決方案。

解決方案二

該方案不是只使用一個密碼管理器結構體,我們使用兩個結構體,一個鎖定的密碼管理器結構體和一個解鎖的密碼管理器結構體:

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

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

那么我們就可以定義兩個單獨的實現塊:

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的實現塊中,包含一個構造函數,一個返回UnlockedPasswordManager結構體的unlock方法,它還包含encryption方法和version方法。同時,我們將lock方法、list_passwords方法和add_password方法移動到了UnlockedPasswordManager實現塊中:

impl UnlockedPasswordManager {
    // 對密碼管理器加鎖
    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結構體,UnlockedPasswordManager還必須實現encryption和version方法,因為這些方法是通用的。

現在我們的API已經更新了,讓我們看看如何在Main中使用它:

首先,我們修改PasswordManager為LockedPasswordManager,這將自動給出編譯時錯誤,因為list_passwords方法和lock方法在LockedPasswordManager上不可用。這很好,因為用戶只能訪問在鎖定狀態下有意義的方法。修改main函數的代碼,如下:

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

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

注意,在這兩個結構體中有相當多的重復代碼,包含相同的字段。這兩個結構體也必須實現兩個狀態之間共同的功能,encryption方法和version方法。

我們希望保留編譯時檢查,但不需要所有這些重復的代碼。下面我們使用泛型和零大小類型來實現這一點。

解決方案三

我們重新定義結構體:

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

struct Locked;
struct Unlocked;

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

首先,我們回到了使用一個名為PasswordManager的結構體,在結構體中添加了一個名為State的新字段,類型為PhantomData。

同時還創建了兩個單元結構體Locked和Unlocked,來表示鎖定和解鎖狀態。

接下來,我們還向PasswordManager結構體添加一個泛型參數State,并將其默認值設置為Locked。

添加泛型形參會導致一個問題,我們必須在結構體的某個地方使用泛型形參,問題是我們并不關心這個泛型參數,我們只使用它來創建不同的類型。

這就是PhantomData的來源,PhantomData是一種零大小類型,只是用于標記。在編譯時,這個字段實際上會被優化掉,這就是為什么PhantomData被稱為零大小類型,因為它不占用空間。

我們在實例化PasswordManager時,必須將這個泛型形參替換為一個具體類型,默認為Locked結構體,也可能是Unlocked結構體。

這么做這是有益的,因為鎖定的密碼管理器不等于解鎖的密碼管理器,這是兩種不同的類型,這意味著我們可以在每種類型上實現不同的方法。現在我們已經重新定義了PasswordManager,讓我們來修改實現塊:

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> {
    // 對密碼管理器加鎖
    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(),
        }
    }
}

我們已經完成了密碼管理器的實現,讓我們繼續修改Main函數:

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

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

文章轉自微信公眾號@coding到燈火闌珊

上一篇:

如何通過CD平臺如何將數據以API的方式同步到facebook

下一篇:

深入探索 Rust Salvo:從簡單博客系統到完整 RESTful API 的實戰項目
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費