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状态,分别由两种类型表示:TicketDraftTicket

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。

由于TicketDraftTicket中的每个字段都有自己的约束条件,因此我们不必在两种类型中重复逻辑。

 

练习

 

原题

// 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)
    }
}

 

这一节的学习就先到这里了。

阅读剩余
THE END