《Effective Ruby:改善Ruby程序的48条建议》一第10条:推荐使用Struct而非Hash存储结构化数据

简介:

本节书摘来自华章出版社《Effective Ruby:改善Ruby程序的48条建议》一书中的第2章,第2.5节,作者 [美]彼得 J.琼斯(Peter J. Jones),更多章节内容可以访问云栖社区“华章计算机”公众号查看

第10条:推荐使用Struct而非Hash存储结构化数据

哈希表是Ruby程序员经常使用的一种有用的、通用的数据结构。Hash类提供了使用哈希表的简单的接口,与数组一样,它是Ruby的重要部分之一,该类有自己专用的语法来创建新的实例。当需要使用键值对时,Hash类绝对是首选。
事实上,Ruby程序员在任何时候都会使用哈希,甚至方法的参数关键字也是使用Hash类语法糖来实现的。哈希如此通用,因此能被用来对类型进行模拟,比如数组、集合,甚至基本对象。在OOP语言中,当用到结构化数据时,我们往往有比哈希更好的选择,在Ruby中也不例外。让我们看一个典型的使用Hash类的例子,然后思考如何替换掉这个结构以使其更合适。
假定你对探索天气数据感兴趣,并从本地气象局获取了年度天气数据。拿着海洋与大气管理局(NOAA)提供的一个CSV文件,你计划将其载入数组并进行分析。CSV文件中每行数据都包含一个月的温度统计信息。你决定提取每行中你感兴趣的一些列,并用哈希数组来存储。思考一下:
image

这里没什么特殊的。CSV文件中的每一行都被翻译为一个哈希并随后插入数组。完成initialize方法后,你将获得一个固定格式的哈希数组,即所有的哈希对象都包含相同的键,不过值是不同的。本质上讲,这个哈希数组表示的是一些对象的集合,不能通过getter方法访问其属性,要访问其属性,你得通过哈希索引操作符来完成。这可能是个小问题,不过它会对AnnualWeather类的接口产生重大影响。
你不应该将这个哈希数组通过公共接口向外暴露,因为每个哈希的键都包含内部实现细节。如果没有类级别的文档,其他程序员将不得不读完整个initialize方法的定义才能知道哪个键对应着CSV中的哪一列。如果initialize是设置键值的唯一方法,那这可能不会造成很大的负担,然而当类逐渐成熟起来,情况可能会有所变化。这就是使用哈希模拟对象表达结构化数据时的缺点,哈希作为公共接口时是没有访问限制的。
每次你想在类内部使用该哈希时,你得回头看看initialize方法从而想起哪些键是可用的。同样的,只要键值的设置都在一个方法内,就没有太大的负担。不过我们来思考一下这样的场景:你尝试创建一个新键。在@readings数组里,每个月的数据都有最高温度和最低温度。你想知道一年里的平均温度,因此你也需要知道每个月的平均
温度。
image

计算每个月的平均温度非常简单。即使如此,如果@readings数组中的每个对象都能响应一个mean方法计算平均值,将会把那段逻辑从这个方法中抽象出去。将这个方法塞进每个哈希里是可以做到的,但是在本例中会不必要地使代码难以理解。(毕竟我们不是在写JavaScript代码。)
在Ruby中使用哈希代替特别简单的类是经常发生的。有时这完全没有问题,但是通常我们真的应该为这类对象创建其专门的类型。如果你和我一样懒,认为创建一个新类这么简单的事情似乎是一种不必要的杂活儿,很幸运,这正是Struct类可以帮我们
做的。
表面上看,使用Struct类很像是在C++中创建一个新的struct类型。然而你如果深挖一下,就会发现这更像是生成类而非数据结构。使用Struct非常简单,只需调用Struct::new方法并附上属性列表。该方法的返回值几乎就是一个新类,它包含每个属性的getter和setter方法。新类也有initialize方法,能够分别初始化每个属性
的值。
为了替换目前使用的哈希,我们只需在initialize方法中做一点微小的改变。
image

如你所见,将Struct::new方法的返回值赋给一个常量是常见的实践。这允许你像类一样使用这个常量,并利用它创建对象。这个单行的代码也让你能清楚地知道这个新类产生的对象提供了哪些方法。这是对特殊的哈希的重大改进。让我们看看这个变化对mean方法的影响。
image

mean方法不需要改变太多,但现在它看起来更有面向对象编程的感觉了。通过getter方法访问属性high和low也有一点副作用。如果把属性名拼错,会引发一个NoMethodError异常。使用哈希时不会有这个问题,因为在哈希中试图访问非法的键只会返回nil而不会引发异常,不过这往往意味着在之后的代码中,你将被卷入一个更难发现的TypeError异常。
Struct类本身比你第一次使用它时更强大。除了属性列表,Struct::new方法还能接收一个可选的块,在新类的上下文中被评价执行。这很拗口,但它实质上是说,我们能在块中定义实例方法和类方法。比如,下面是我们如何定义mean方法的:
image

当你觉得创建一个新类过于笨重时,Struct就非常有用了。哈希只能定义一堆相同格式的无关的键,Struct却能让你定义实例方法和类方法。当你想为对象增加一些简单操作时,这完美极了。它们的公共接口也是类型良好的,更适合于AnnualWeather类的使用者。通过为@readings数组开启attr_reader方法,可以解决之前对通过AnnualWeather向外暴露一个哈希数组的顾虑。我不得不说这是一个大的改进。
要点回顾
在处理结构化数据时,如果创建一个新类不那么合适时,推荐使用Struct而非Hash。
将Struct::new的返回值赋给常量,并像类一样使用它。

相关文章
|
4月前
|
存储 Ruby 索引
|
4月前
|
Ruby 索引
|
存储 Ruby
【Ruby on Rails全栈课程】2.8 ruby的数据结构--哈希(Hash)
1、哈希(Hash) 哈希也是存储对象的一个集合,哈希里面的元素是以"key" => “value”(键值对)这样的形式存在的,元素是没有顺序的,哈希的键可以是任意对象,键必须的唯一的,键通常用符号(Symbol)表示。 哈希的创建有两种形式,两种形式都是一样的,最常使用第二种:
156 0
|
测试技术 C++ Ruby
C++程序中嵌入Ruby脚本系统
Ruby,一种为简单快捷面向对象编程(面向对象程序设计)而创的脚本语言,由日本人松本行弘(まつもとゆきひろ,英译:Yukihiro Matsumoto,外号matz)开发,遵守GPL协议和Ruby License。
1559 0
|
消息中间件 安全 Ruby
Nanite:Ruby程序的一个自我装配集群
本文讲的是Nanite:Ruby程序的一个自我装配集群,Nanite(由Ezra Zygmuntowicz开发)是Engine Yard云计算策略的一个新兵:它是“Ruby程序的一个自我装配集群”,用以构筑高度可伸缩的Web应用的后端。
1096 0
|
程序员 Ruby
《Effective Ruby:改善Ruby程序的48条建议》一导读
学习一门新的编程语言通常需要经过两个阶段。第一阶段是学习这门编程语言的语法和结构。如果我们具有其他编程语言的经验,这个阶段通常只需要很短的时间。以Ruby为例,接触过其他面向对象语言的程序员对Ruby的语法也会比较熟悉。有经验的程序员对于语言的结构(如何根据语法构建应用程序)是很熟悉的。
1207 0