首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一种新的设计模式:newtype

一种新的设计模式:newtype

作者头像
MikeLoveRust
发布2020-11-06 11:48:19
发布2020-11-06 11:48:19
9960
举报

概述

设计模式是在编写代码时在各种不同情况下出现的模式。在本文中,我将讨论 Newtype 设计模式。具体来说,我将以 Rust 编程语言为背景来讨论它,以及如何解决在 Rust 中使用 Newtype 模式时出现的一些问题。

Rust 的设计模式

编程设计模式是指在编写代码时在各种不同情况下出现的模式,这并不是说有了设计模式你就不需要自己去思考问题,而是设计模式给你提供了一个工具箱,帮助你去思考解决方案。

不同的编程语言有不同的表达方式。关于设计模式的经典书籍《设计模式,Elements of Reusable Object-Oriented Software》,围绕面向对象的 C++ 和 Smalltalk 为例介绍各种模式。虽然这些模式中的大部分仍然适用于其他面向对象的编程语言,但它们可能需要进行调整才能使它们很好地工作。

Rust 是一门有趣的编程语言,因为该语言的设计借鉴了面向对象、过程式和函数式编程语言的思想。这意味着有不同的模式是有用的,现有的模式可能会以一种新的方式更好地表达。

在这篇文章中,我将解释一个我在 Rust 代码中发现有用的模式:Newtype 模式。

问题描述:基本数据类型是非描述性的

想象一下,我们正在编写一个大型代码库。像许多项目一样,项目包括一些用户信息,所以有一个结构,如下。

代码语言:javascript
复制
pub struct Person {
    pub name: String,
    pub phone_number: String,
    pub id_number: String,
    pub age: u32
}

几个月后,在代码库的另一个角落看到一些代码,这可能从数据库中删除一个人 ,函数参数如下:

代码语言:javascript
复制
pub fn load_person(person: String) -> Result<Person>;

汗……那个参数是什么字段?这个人的身份证 ID 吗?还是他的名字?

还有年龄字段可能也会让人迷糊,比如说,你会如何实现这个函数?

代码语言:javascript
复制
pub fn time_to_retirement(current_age: u32) -> u32;

是以年为单位的年龄?一般情况下,时间戳都是以秒为单位存储的,所以可能是传递一个以秒为单位的年龄?

Newtype 设计模式

Newtype 模式是这样场景,一个结构体里面有很多基本类型。

让我们看看如何将它应用到 person 例子中。

你首先要定义 Newtype。设计模式只是一个值,包裹在一个结构中。

代码语言:javascript
复制
pub struct Name(String);
pub struct PhoneNumber(String);
pub struct IdNumber(String);
pub struct Years(u32);

如果你没见过这样的结构体,字段没有命名,这个结构体叫 tuple struct。Newtype 是它的一个特例,只有一个字段。

然后你可以开始在你的Person结构中使用你的新类型。

代码语言:javascript
复制
pub struct Person {
    pub name: Name,
    pub phone_number: PhoneNumber,
    pub id_number: IdNumber,
    pub age: Years
}

好处显而易见,我们的 load_person 函数更加清晰。比如类型是 IdNumber 而不是 String,你就知道我们要传入这个人的 ID 号。

代码语言:javascript
复制
pub fn load_person(person: IdNumber) -> Result<Person>;

年龄字段现在也更清晰了,Years 类型使得我们的年龄很明显是以年为单位,而不是以秒为单位。

代码语言:javascript
复制
pub fn time_to_retirement(current_age: Years) -> Years;

字符串是 Newtypes 的常见用例,因为你可以用它们来增加对字符串格式化的验证。例如有些国家身份证号有特定的格式,因此可以更方便对其进行验证。

构建示例

代码语言:javascript
复制
pub struct PhoneNumber(String);
impl PhoneNumber {
    pub fn new(s: String) -> PhoneNumber {
        PhoneNumber(s)
    }
    pub fn as_str(&self) -> &str {
        // We didn't name the inner type, so it follows the same
        // naming convention as tuples. In other words, the inner
        // field is called `0`.
        &self.0
    }
}

fn main() {
    let num = PhoneNumber::new("555-1234".to_string());
    println!("{}", num.as_str())
}

构建及 parse 身份证号例子

代码语言:javascript
复制
// cargo-deps: derive_more = "0.99"
extern crate derive_more;

use derive_more::{Display, Deref};
#[derive(Display, Debug, Deref, PartialEq)]
pub struct IdNumber(String);

use std::str::FromStr;
impl FromStr for IdNumber {
    type Err = IdNumberParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() != 13 {
            Err(IdNumberParseError::InvalidFormat)
        } else {
            Ok(IdNumber(s.to_string()))
        }
    }
}

#[derive(Display, Debug, PartialEq)]
pub enum IdNumberParseError {
    InvalidFormat
}
impl std::error::Error for IdNumberParseError {}

fn main() {
    let id = IdNumber::from_str("12345");
    assert_eq!(id, Err(IdNumberParseError::InvalidFormat));

    let id = IdNumber::from_str("1234567890123").unwrap();

    println!("My ID Number is {}", id);
}

很简单吧,是不是又轻松 get 到了一种新的设计模式?有什么感想欢迎留言。

英文原文:

https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档