60、两个状态(开发思维)
Ticket ID
让我们再想想我们的票务管理系统。
我们现在的Ticket模型如下:
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status
}
这里却少了一件事:用于唯一标识ticket的identifier(标识符)。
对于每个ticket,该标识符应该是唯一的。这可以通过在创建新的ticket时自动生成它来保证。
改进模型
id应该存储在哪里?
我们可以为Ticket
结构体添加一个新的字段:
pub struct Ticket {
pub id: TicketId,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status
}
但是在创建工单之前,我们不知道ID。所以,它不可能一开始就在那里。
这样说来,ID这个字段,应该是可选的:
pub struct Ticket {
pub id: Option<TicketId>,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status
}
其实这样做也不是特别的好——每次从存储里检索ticket的时候,我们都必须处理None
情况,即使我们知道创建ticket后,id应该始终存在。
最佳的解决方案是有两种不同的ticket状态,分别由两种类型表示:TicketDraft
和Ticket
:
pub struct TicketDraft {
pub title: TicketTitle,
pub description: TicketDescription
}
pub struct Ticket {
pub id: TicketId,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status
}
TicketDraft
是尚未创建的ticket。它没有ID,也没有status。
Ticket
是已经创建的ticket。它有一个ID和一个status。
由于TicketDraft
和Ticket
中的每个字段都有自己的约束条件,因此我们不必在两种类型中重复逻辑。
练习
原题
// TODO: Update `add_ticket`'s signature: it should take a `TicketDraft` as input
// and return a `TicketId` as output.
// Each ticket should have a unique id, generated by `TicketStore`.
// Feel free to modify `TicketStore` fields, if needed.
//
// You also need to add a `get` method that takes as input a `TicketId`
// and returns an `Option<&Ticket>`.
// TODO: 更新 add_ticket 的签名:它应接收一个 TicketDraft 作为输入,
// 并返回一个 TicketId 作为输出。
// 每个票据应具有唯一的 ID,由 TicketStore 生成。
// 如果需要,可以修改 TicketStore 的字段。
//
// 同时还需要添加一个 get 方法,它接收一个 TicketId 作为输入,
// 并返回一个 Option<&Ticket>。
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
/* TODO */
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub id: TicketId,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TicketDraft {
pub title: TicketTitle,
pub description: TicketDescription,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
/* TODO */
}
}
/* TODO */
pub fn add_ticket(&mut self, ticket: Ticket) {
self.tickets.push(ticket);
}
/* TODO */
}
我们一共要修改四处代码,第一处是为TicketStore
添加某些东西,但是添加什么呢?我也不知道……
第二处是修改TicketStore
的构造函数,估计这个也是和上面那个添加东西的一样,我也不知道要添加什么……
第三处是修改add_ticket
的代码,接收一个 TicketDraft
作为输入,并返回一个 TicketId
作为输出。
第四处应该就是添加一个 get 方法,它接收一个 TicketId
作为输入,并返回一个 Option<&Ticket>
。
不知道从哪下手,直接学习答案吧(
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
counter: u64,
}
首先,它给TicketStore
添加了一个字段counter
,类型为u64。
紧接着就是构造函数:
pub fn new() -> Self {
Self {
tickets: Vec::new(),
counter: 0,
}
}
初始化counter
为0。
然后是add_ticket
方法:
//很明显这里把第二个参数从Ticket类型改为了TicketDraft类型
pub fn add_ticket(&mut self, draft: TicketDraft) -> TicketId {
let id = self.counter;//先获取当前的ID号
self.counter += 1;//然后让当前TicketStore的ID号加一
let ticket = Ticket {
id: TicketId(id),//用结构体TicketId构造了这个字段
title: draft.title,
description: draft.description,
status: Status::ToDo,//初始化status
};
self.tickets.push(ticket);//把新创建的ticket放到vec里面
TicketId(id)//返回TicketId
}
所以这个方法做的操作就是根据用户提供的ticket的基本信息,让系统自动分配一个ID给它,然后赋值给它一个初始的状态ToDo
,最后把新放进去的ticket的ID作为返回值返回给函数的调用者。
最后这个就是一个get方法:
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
self.tickets.iter().find(|ticket| ticket.id == id)
}
哦,这个就是根据TicketId
来获取对应的ticket
,然后返回一个Option
枚举,以应对None的情况。这样看来,整个程序的逻辑还挺清晰的。
我一开始还以为它还要用类似UUID的方式生成全局唯一的ID呢,没想到就是自增索引,果然一开始的思路不在一条线上的是很难看懂的,我还纳闷呢,你让我给结构体添加字段,添加啥啊,用添加吗?看来他这个思路还可以,就是,怎么说呢,或许是我的想法和大多数人的想法不太一样吧(
完成的结果就是这样子了:
// TODO: Update `add_ticket`'s signature: it should take a `TicketDraft` as input
// and return a `TicketId` as output.
// Each ticket should have a unique id, generated by `TicketStore`.
// Feel free to modify `TicketStore` fields, if needed.
//
// You also need to add a `get` method that takes as input a `TicketId`
// and returns an `Option<&Ticket>`.
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
counter: u64,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub id: TicketId,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TicketDraft {
pub title: TicketTitle,
pub description: TicketDescription,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
counter: 0,
}
}
pub fn add_ticket(&mut self, draft: TicketDraft) -> TicketId {
let id = self.counter;
self.counter += 1;
let ticket = Ticket {
id: TicketId(id),
title: draft.title,
description: draft.description,
status: Status::ToDo,
};
self.tickets.push(ticket);
TicketId(id)
}
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
self.tickets.iter().find(|ticket| ticket.id == id)
}
}
这一节的学习就先到这里了。