《面向对象设计实践指南:Ruby语言描述》—第8章 8.4节组合成Bicycle

简介: Bicycle有一个Parts,而Parts依次有一个Part对象集合。Parts和Part都可以以类形式存在,但包含它们的对象会把它们当成角色。Parts是一个扮演Parts角色的类,它实现了spares。

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

8.4 组合成Bicycle
面向对象设计实践指南:Ruby语言描述

下面的代码展示了Bicycle使用组合的情况。它展示了Bicycle、Parts、PartsFactory,以及针对公路和山地自行车的设置数组。

Bicycle有一个Parts,而Parts依次有一个Part对象集合。Parts和Part都可以以类形式存在,但包含它们的对象会把它们当成角色。Parts是一个扮演Parts角色的类,它实现了spares。而Part的角色则由OpenStruct扮演,它会实现name、description和needs_spare。

下面的54行代码可以完全取代第6章里的那个66行继承层次结构。

1  class Bicycle
2    attr_reader :size, :parts
3  
4    def initialize(args ={})
5     @size    = args[:size]
6     @parts    = args[:parts]
7    end
8  
9    def spares
10     parts.spares
11    end
12  end
13  
14  require 'forwardable'
15  class Parts
16    extend Forwardable
17    def_delegators :@parts, :size, :each
18    include Enumerable
19  
20    def initialize(parts)
21     @parts = parts
22    end
23  
24    def spares
25     select {|part| part.needs_spare}
26    end
27  end
28  
29  require 'ostruct'
30  module PartsFactory
31    defself.build(config, parts_class = Parts)
32     parts_class.new(
33       config.collect {|part_config|
34        create_part(part_config)})
35    end
36  
37    defself.create_part(part_config)
38     OpenStruct.new(
39       name:     part_config[0],
40       description: part_config[1],
41       needs_spare: part_config.fetch(2, true))
42    end
43  end
44  
45  road_config =
46    [['chain',     '10-speed'],
47    ['tire_size',    '23'],
48    ['tape_color',    'red']]
49  
50  mountain_config =
51   [['chain',      '10-speed'],
52    ['tire_size',    '2.1'],
53    ['front_shock',   'Manitou', false],
54    ['rear_shock',    'Fox']]

这段新的代码与之前的那个Bicycle层次结构很像。唯一的区别在于:那个spares信息现在会返回一个像Part对象的数组,而不是返回一个散列表。如下面的第7行和第15行所示。

1  road_bike = 
2    Bicycle.new(
3     size: 'L',
4     parts: PartsFactory.build(road_config))
5  
6  Road_bike.spares
7
8  # -> [#<OpenStruct PartsFactory::Part name="chain"……
9  mountain_bike =
10    Bicycle.new(
11     size: 'L',
12     parts: PartsFactory.build(mountain_config))
13  
14  mountain_bike.spares
15  # -> [#<OpenStruct name="chain"……

既然有了这些新类,那么创建新类型的自行车便是件轻而易举的事情。

在第6章,添加对卧式自行车的支持占用了19行新代码。现在,这项任务只使用3行的配置即可完成(如下面第2~4行)。

1  recumbent_config =
2   [['chain',    '9-speed'] ,
3    ['tire_size',  '28'] ,
4    ['flag',     'tall and orange']]
5  
6  recumbent_bike = 
7    Bicycle.new(
8     size: 'L', 
9     parts: PartsFactory.build(recumbent_config))
10  
11  recumbent_bike.spares
12  # -> [#<OpenStruct 
13  #    name="chain", 
14  #    description="9-speed", 
15  #    needs_spare=true>, 
16  #   #<OpenStruct 
17  #    name="tire_size", 
18  #    description="28", 
19  #    needs_spare=true>, 
20  #   #<OpenStruct 
21  #    name="flag", 
22  #    description="tall and orange", 
23  #    needs_spare=true>]

如上面的第11~23行所示,只需简单地对其零件进行描述,你便可以创建出一辆新的自行车。

聚合:一种特殊的组合

你已经对术语“委托”(delegation)有所了解。委托指的是当某个对象接收到消息时,它仅仅是将其转发给另外一个对象。委托会创建依赖关系:接收对象必须要能识别出这条消息,并要知道将它寄到哪里去。

组合通常会涉及委托,但这个术语还包含了更多的内容。一个组合对象由多个部分组成,它期望着通过定义良好的接口与这些单个部分进行交互。

组合描述了“有一个”关系。吃饭要有多样开胃菜,大学里有很多院系,自行车有多个零件。吃饭、大学和自行车都由多个对象组合而成。开胃菜、院系和零件都是角色。组合对象依赖于角色的接口。

因为吃饭与开胃菜之间是使用接口进行交互,所以希望表现为开胃菜的新对象只需要实现这个接口即可。不曾预料到的开胃菜可以“无缝地”出现在餐桌上,并且随时可更换。

术语“组合”(composition)可能让人感到有点迷惑,因为它被用在了两个稍有差异的概念身上。上面的定义是此术语最为广泛的用法。在大多数情况下,当你看到“组合”时,它通常都是指明两个对象之间的“有一个”关系。

不过,其正式的定义则表示了更为具体的内容。它表明的是这样一种关系,即被包含的对象离开了其容器就“活不下去”。严格意义上讲,你不仅要知道吃饭会有多种开胃菜,而且还要知道一旦开吃,那些开胃菜也会消失。

在这个定义里有一个缺口,正好可以由“聚合”(aggregation)一词补上。聚合基本上就是组合,不同之处在于那个被包含对象有自己独立的生命。大学有许多院系,而院系又进一步有许多教授。如果你的应用程序管理了许多大学,并且知道成百上千的教授,那么也会理所当然地期望:虽然某个院系在其所在的大学倒闭时会完全消失,但它的教授仍会继续存在。

这种“大学-院系”关系是一种组合(严格意义上来讲),而“院系-教授”关系是聚合。取消一个院系并不会连它的教授也会消失。他们有自己的存在方式和生命。

组合与聚合之间的这种区别对你的代码并没什么实际的影响。既然已熟悉这两个术语,那么你可以使用组合来指代这两种关系,的确需要时再进行区分。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

相关文章
|
算法 测试技术 API
如何用Ruby语言提高代码的可读性和可维护性
在软件开发过程中,代码的可读性和可维护性是非常重要的因素。一旦代码变得难以理解和修改,就会导致开发速度变慢、bug增多以及团队合作效率下降。在本篇博客中,我们将探讨一些使用Ruby编程语言的技巧和最佳实践,以提高代码的可读性和可维护性。
90 1
|
XML 前端开发 安全
构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController
构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController
|
前端开发 关系型数据库 开发工具
构建自己的MVC框架(Ruby语言实现)-- 开篇
构建自己的MVC框架(Ruby语言实现)-- 开篇
|
5月前
|
文字识别 API 开发工具
印刷文字识别产品使用合集之SDK支持ruby语言吗
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
3月前
|
自然语言处理 Ruby
Ruby语言揭秘:如何轻松驾驭自然语言处理?
【8月更文挑战第31天】本文介绍了如何利用Ruby进行自然语言处理(NLP),包括安装`treat`库及其实现文本分词、词性标注和命名实体识别等基础任务的方法。通过示例代码,展示了Ruby在NLP领域的强大功能与灵活性,为初学者提供了一个友好且实用的入门指南。在信息爆炸的今天,掌握Ruby进行NLP处理的技能显得尤为重要。
30 0
|
3月前
|
测试技术 开发者 Ruby
Ruby领域特定语言(DSL)的神秘力量:如何让你的代码飞起来?
【8月更文挑战第31天】在软件开发领域,Ruby以简洁的语法和强大的表达能力著称,但面对复杂业务逻辑时,代码可能变得冗长难维护。此时,领域特定语言(DSL)成为解决问题的有效途径。DSL专为特定领域设计,使代码更贴近业务逻辑,提高可读性和可维护性。本文通过示例展示了如何在Ruby中开发DSL,包括使用解析器和宏功能创建一个简单的Web应用结构描述语言,并提出了定义清晰语法、避免滥用DSL等最佳实践,强调了测试与文档的重要性。
70 0
|
4月前
|
监控 网络安全 持续交付
公司电脑管理软件结合 Ruby 语言的实践探索
在数字化办公时代, Ruby 语言以简洁灵活的特点, 成为公司电脑管理软件开发的新选择。通过示例代码展示了 Ruby 在获取系统信息、监控内存使用及远程管理方面的应用。Ruby 的灵活性与丰富库资源, 大大提升了管理效率与准确性, 为企业数字化管理带来新可能。
38 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