查看原文
其他

一种新的设计模式:newtype

Justin Wernick 高可用架构 2020-11-06

概述


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


Rust 的设计模式


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


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


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


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


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


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


pub struct Person { pub name: String, pub phone_number: String, pub id_number: String, pub age: u32}


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


pub fn load_person(person: String) -> Result<Person>;


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


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


pub fn time_to_retirement(current_age: u32) -> u32;


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


Newtype 设计模式


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


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


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


pub struct Name(String);pub struct PhoneNumber(String);pub struct IdNumber(String);pub struct Years(u32);

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


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


pub struct Person { pub name: Name, pub phone_number: PhoneNumber, pub id_number: IdNumber, pub age: Years}


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


pub fn load_person(person: IdNumber) -> Result<Person>;


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


pub fn time_to_retirement(current_age: Years) -> Years;


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


构建示例


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 身份证号例子


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

参考阅读:



本文由高可用架构翻译,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。


高可用架构
改变互联网的构建方式

长按二维码 关注「高可用架构」公众号

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存