《面向对象设计实践指南:Ruby语言描述》—第8章 8.3节制造Parts

简介: 描述构成特定自行车的零件组合比较容易。下面的代码使用一个简单的二维数组来实现了这点,其中每一行包含了三种可能的列。

本节书摘来自异步社区《面向对象设计实践指南:Ruby语言描述》一书中的第8章,第8.3节制造Parts,作者【美】Sandi Metz,更多章节内容可以访问云栖社区“异步社区”公众号查看。

8.3 制造Parts
面向对象设计实践指南:Ruby语言描述
回顾一下上面的第4~7行。那些Part对象存放在chain、mountain_tire等变量里面。它们都是很久以前创建的,你可能已经把它们给忘了。请仔细想想这四行所代表的知识主体。在应用程序里的某个地方,会有对象必须要知道如何创建这些Part对象。而在上面的第4~7行,在那个地方必须要知道与山地自行车一起的这四个特定对象。

这里包含了很多的知识,它很容易在应用程序里泄漏掉。这种泄漏情况,既不幸也没必要。虽然有很多不同的单个零件,但有效的零件组合很少。如果你能描述不同的自行车,并且使用这些描述神奇般地为任何自行车制造出正确的Parts对象,那么一切都很简单了。

描述构成特定自行车的零件组合比较容易。下面的代码使用一个简单的二维数组来实现了这点,其中每一行包含了三种可能的列。第一列包含了零件名称(如'chain'、'tire_size'等),第二列是零件描述(如'10-speed'、'23'等),第三列(可选)是一个布尔值,用以指示这个零件是否需要留一个备用。只有下面第9行的'front_shock'会在第三列设置值,其他零件都默认为true,因为它们需要备用。

1  road_config =
2    [['chain',     '10-speed'],
3    ['tire_size',    '23'] ,
4    ['tape_color',    'red']]
5  
6  mountain_config =
7    [['chain',     '10-speed'] ,
8    ['tire_size',    '2.1'] ,
9    ['front_shock',    'Manitou', false] ,
10   ['rear_shock',    'Fox']]

与散列表有所不同,这个简单的二维数组没有提供结构信息。但是,你明白这个结构是如何组织的,你可以将你的知识转化为一个制造Parts的新对象。

8.3.1 创建PartsFactory
正如在第3章做过的讨论,制造其他对象的对象叫工厂。当听到这个词时,你过去在其他语言上的经历很可能会让你有所退却,但是现在请把它当作是一次重拾信心的机会。“工厂”一词并不表示有多困难,或是过于复杂。它只是一个词组,OO设计师常用它来简明地交流像“一个对象创建其他多种对象”这样的思想。Ruby的工厂很简单,没有理由怕这只“纸老虎”。

下面的代码展示了一个新的PartsFactory模块。它的工作是接收一个数组(上面所列出的数组当中的某一个),并且制造出一个Parts对象。采用这种方式,它也可以顺便创建Part对象,但这个动作是私有的。其公开的责任是创建一个Parts。

这是PartsFactory的第一版,它会接收三个参数,即config和分别用于Part跟Parts的类名。下面第6行用于创建新的Parts实例,它会使用根据config里的信息所建立的Part对象数组来执行初始化操作。

1  module PartsFactory
2    def self.build(config, 
3           part_class = Part,
4           parts_class = Parts)
5
6     parts_class.new(
7       config.collect {|part_config|
8        part_class.new(
9          name:     part_config[0] ,
10         description: part_config[1] ,
11         needs_spare: part_config.fetch(2, true))})
12    end
13  end

这个工厂知道 config 数组的结构。在上面的第 9~11 行,它期望 name 在第一列,description在第二列,而needs_spare在第三列。

将config的结构知识放置在这个工厂里,会有两种后果。第一个,config可以表达得非常简洁。因为PartsFactory了解config的内部结构,所以config可被指定为数组,而不用指定为散列表。第二个,一旦决定让config保持为数组,那么你就应该一直使用这个工厂来创建新的Parts对象。通过其他机制来创建新的Parts,需要复制编码在上面第9~11行里的知识。

既然有了PartsFactory,那么你就可以使用上面定义的设置数组轻松地创建新的Parts。如下所示。

1  road_parts = PartsFactory.build(road_config)
2  # -> [#<Part:0x00000101825b70
3  #    @name="chain",
4  #    @description="10-speed",
5  #    @needs_spare=true>,
6  #   #<Part:0x00000101825b20
7  #    @name="tire_size",
8  #      etc ...
9  
10  Mountain_parts = PartsFactory.build(mountain_config)
11  # -> [#<Part:0x0000010181ea28
12  #     @name="chain",
13  #     @description="10-speed",
14  #     @needs_spare=true>,
15  #   #<Part:0x0000010181e9d8
16  #     @name="tire_size",
17  #     etc ...
在```  
PartsFactory与新的设置数组相结合之后,它会将所有创建有效Parts所需要的知识隔离起来。这种信息之前分散在整个应用程序里,但现在它被包含在这里的一个类和两个数组里。

**8.3.2 借助PartsFactory**
既然PartsFactory已被建立好,并可以运行起来,那么接下来看看Part类(重复如下)。它很简单。不仅如此,就连在PartsFactory里那段唯一有些复杂的代码(下面第7行的fetch)也被复制了过来。如果PartsFactory创建了所有的Part,那么Part就不会再需要这段代码。如果将这段代码从Part里删除,那么里面几乎什么都没了。可以将整个Part类更换为简单的OpenStruct。

1  class Part
2    attr_reader :name, :description, :needs_spare
3  
4    def initialize(args)
5     @name     = args[:name]
6     @description = args[:description]
7     @needs_spare = args.fetch(:needs_spare, true)
8    end
9  end

Ruby的OpenStruct类与见过的那个Struct类很像,它提供了一种便捷方式,可以将若干属性汇集到一个对象。这两者的区别在于:Struct接收的是按位置顺序排列的初始化参数,而OpenStruct在初始化时是接收一个散列表,然后从该散列表派生出属性。

删除Part类的理由很充分。这样做能简化代码,并且你可能永远不再需要像当前那样复杂的代码。删除Part类,并更改PartsFactory,以使用OpenStruct来创建扮演Part角色的对象,通过这样的方式你便可以清除掉Part的所有痕迹。下面的代码展示了一个新版本的PartFactory,其中零件的创建已被重构为它自己的一个方法(第9行)。

1  require 'ostruct'
2  module PartsFactory
3    def self.build(config, parts_class = Parts)
4     parts_class.new(
5       config.collect {|part_config|
6        create_part(part_config)})
7    end
8  
9    def self.create_part(part_config)
10     OpenStruct.new(
11       name:     part_config[0] ,
12       description: part_config[1] ,
13       needs_spare: part_config.fetch(2, true))
14    end
15  end

上面的第13行,是这个应用程序里唯一的一处让needs_spare默认为true的地方。因此,PartsFactory必须全权负责制造Parts。

这个新版的PartsFactory很有效。如下所示,它会返回一个Parts,其中包含一个OpenStruct对象数组,而且每一个对象都扮演了Part角色。

1  mountain_parts = PartsFactory.build(mountain_config)
2  # ->


相关文章
|
算法 测试技术 API
如何用Ruby语言提高代码的可读性和可维护性
在软件开发过程中,代码的可读性和可维护性是非常重要的因素。一旦代码变得难以理解和修改,就会导致开发速度变慢、bug增多以及团队合作效率下降。在本篇博客中,我们将探讨一些使用Ruby编程语言的技巧和最佳实践,以提高代码的可读性和可维护性。
93 1
|
XML 前端开发 安全
构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController
构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController
|
前端开发 关系型数据库 开发工具
构建自己的MVC框架(Ruby语言实现)-- 开篇
构建自己的MVC框架(Ruby语言实现)-- 开篇
|
6月前
|
文字识别 API 开发工具
印刷文字识别产品使用合集之SDK支持ruby语言吗
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
4月前
|
自然语言处理 Ruby
Ruby语言揭秘:如何轻松驾驭自然语言处理?
【8月更文挑战第31天】本文介绍了如何利用Ruby进行自然语言处理(NLP),包括安装`treat`库及其实现文本分词、词性标注和命名实体识别等基础任务的方法。通过示例代码,展示了Ruby在NLP领域的强大功能与灵活性,为初学者提供了一个友好且实用的入门指南。在信息爆炸的今天,掌握Ruby进行NLP处理的技能显得尤为重要。
30 0
|
4月前
|
测试技术 开发者 Ruby
Ruby领域特定语言(DSL)的神秘力量:如何让你的代码飞起来?
【8月更文挑战第31天】在软件开发领域,Ruby以简洁的语法和强大的表达能力著称,但面对复杂业务逻辑时,代码可能变得冗长难维护。此时,领域特定语言(DSL)成为解决问题的有效途径。DSL专为特定领域设计,使代码更贴近业务逻辑,提高可读性和可维护性。本文通过示例展示了如何在Ruby中开发DSL,包括使用解析器和宏功能创建一个简单的Web应用结构描述语言,并提出了定义清晰语法、避免滥用DSL等最佳实践,强调了测试与文档的重要性。
81 0
|
5月前
|
监控 网络安全 持续交付
公司电脑管理软件结合 Ruby 语言的实践探索
在数字化办公时代, Ruby 语言以简洁灵活的特点, 成为公司电脑管理软件开发的新选择。通过示例代码展示了 Ruby 在获取系统信息、监控内存使用及远程管理方面的应用。Ruby 的灵活性与丰富库资源, 大大提升了管理效率与准确性, 为企业数字化管理带来新可能。
39 3
|
前端开发 关系型数据库 开发工具
构建自己的MVC框架(Ruby语言实现)-- 第一章 从零到“它工作了!”
构建自己的MVC框架(Ruby语言实现)-- 第一章 从零到“它工作了!”
|
存储 自然语言处理 Go
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础数据类型(data types)EP02
Ruby是强类型动态语言,即Ruby中一旦某一个对象被定义类型,如果不通过强制转换操作,那么它永远就是该数据类型,并且只有在Ruby解释器运行时才会检测对象数据类型,它的一切皆为对象(包括 nil 值对象),可以通过调用内置class属性来获取该对象的具体数据类型。对于 Ruby 而言,所有类型都继承自 Object 类(根类为 BasicObject)。
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础数据类型(data types)EP02
|
Ruby Python
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01
书接上回,前一篇我们在全平台构建好了Ruby3的开发环境,现在,可以和Ruby3第一次亲密接触了。 Ruby是一门在面向对象层面无所不用其极的解释型编程语言。 我们可以把编写Ruby代码看作是一场行为上的艺术,编码就像跳舞一样,Ruby的每一步都很优雅,几乎没有一步是多余的。
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01