
ASP.NET Web API快速入門介紹
理想情況下,這些方法不應該列出,除非密碼管理器已解鎖。
當前解決方案的問題是它使用運行時檢查,所以在調用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。