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

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。

```javascript
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  # -> <Parts:0x000001009ad8b8 @parts=
3  #   [#<OpenStruct name="chain",
4  #          description="10-speed",
5  #          needs_spare=true>,
6  #    #<OpenStruct name="tire_size",
7  #          description="2.1",
8  #          etc ...

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-08-29 05:47:35

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

《面向对象设计实践指南:Ruby语言描述》目录—导读

内容提要 面向对象设计实践指南:Ruby语言描述 本书是对"如何编写更易维护.更易管理.更讨人喜爱且功能更为强大的Ruby应用程序"的全面指导.为帮助读者解决Ruby代码难以更改和不易扩展的问题,作者在书中运用了多种功能强大和实用的面向对象设计技术,并借助大量简单实用的Ruby示例对这些技术进行全面解释. 全书共9章,主要包含的内容有:如何使用面向对象编程技术编写更易于维护和扩展的Ruby代码,单个Ruby类所应包含的内容,避免将应该保持独立的对象交织在一起,在多个对象之间定义灵活的接

《面向对象设计实践指南:Ruby语言描述》—第1章 1.3节设计行为

1.3 设计行为 面向对象设计实践指南:Ruby语言描述 随着常见设计原则和模式的出现与传播,所有的OOD问题可能都已被解决.既然基础的规则都已知道,那么设计面向对象的软件还会有多难呢? 事实证明,它非常难.如果将软件理解为可定制的家具,那么原则和模式便像是木工的工具.了解软件在完成后会是什么样子,并不能让它自我构建成那个样子.应用程序之所以存在,是因为有程序员使用了这些工具.最终的结果可能是,它要么成为一个漂亮的橱柜,要么成为一张摇摇晃晃的椅子.具体是哪一种结果,则取决于程序员使用设计工具的经

《面向对象设计实践指南:Ruby语言描述》—第1章 1.1节设计赞歌

第1章 面向对象设计 面向对象设计实践指南:Ruby语言描述 世界是过程式的.时间不停在向前流动,而事件也一个接一个地逝去.你每天早上的过程或许就是:起床.刷牙.煮咖啡.穿衣,然后上班.这些活动都可以使用过程软件来建模.因为了解事件的顺序,所以你可以编写代码来完成每一件事情,然后仔细地将这些事情一个接一个地串在一起. 世界也是面向对象的.与你互动的对象可能包括有你的老伴和猫,或者是车库里的旧汽车和一大堆的自行车零件,又或者是你的那颗扑通跳动的心脏,以及用来保持健康的锻炼计划.在这些对象中,每一个

《面向对象设计实践指南:Ruby语言描述》—第1章 1.2节设计工具

1.2 设计工具 面向对象设计实践指南:Ruby语言描述 设计可不是遵循一套固定规则就完事的动作.它是每次沿着一条分支前进的旅行,在这条路径上早期的选择关闭了某些选择,同时又会打开其他新的选择.在设计过程中,你会徘徊于各种错综复杂的需求中,这里的每个关键时刻都代表着一个决策点,它会对将来产生影响. 像雕塑家有凿子和文稿一样,面向对象的设计师也有自己的工具-原则和模式. 1.2.1 设计原则 SOLID原则首先由Michael Feathers提出,再由Robert Martin进行了推广.它代表

《面向对象设计实践指南:Ruby语言描述》—第1章 1.4节 面向对象编程简介

1.4 面向对象编程简介 面向对象设计实践指南:Ruby语言描述 面向对象的应用程序由对象和它们之间传递的消息构成.其中,消息相对更为重要.但在本节的简介里(以及在本书的前面几个章节里),这两个概念都同等重要. 1.4.1 过程式语言 相对于非面向对象(或过程式)的编程来说,面向对象编程是面向对象的.依据这两种风格的差异来考虑它们很有意义.假设有这么一种通用的编程语言,它可用来创建简单的脚本.在这门语言里,你可以定义变量(即组成多个名称),并将这些名字与少量的数据相关联.一旦进行了分配,便可以通

《面向对象设计实践指南:Ruby语言描述》—第1章 1.5节小结

1.5 小结 面向对象设计实践指南:Ruby语言描述 如果某个应用程序存活了很长时间(也就是说,如果它成功了),那么它最大的问题将是如何应对变化.通过代码编排有效地应对变化是设计的事情.最常见的设计要素是原则和模式.不幸的是,即使正确地运用了原则,并且也恰当地使用了模式,也无法保证能够很好地创建出易于更改的应用程序. OO度量能暴露出应用程序在遵循OO设计原则方面的情况.糟糕的度量值强烈地表明将来可能会遭遇困难:不过,好的度量值也发挥不了太大的作用.一个做法有问题的设计也可能产生出很高的度量值,

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

第8章 组合对象 面向对象设计实践指南:Ruby语言描述 组合(composition)是指将不同的部分结合成一个复杂整体的行为,这样整体会变得比单个部分的总和还要大.例如,音乐就是组合而成的. 你可不能将软件当作是音乐,那只是一种类比.贝多芬的第五交响曲乐谱是一长串独特而又独立的记号.你只听一遍就会明白:尽管它包含的是一些记号,但它不是记号.它是另一回事. 你可以按同样的方式来创建软件,使用面向对象的组合技术来将简单.独立的对象组合成更大.更复杂的整体.在组合过程中,较大的那个对象通过"有一个

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

8.2 组合成Parts对象 面向对象设计实践指南:Ruby语言描述 很明显,零件列表会包含一长串的单个零件.现在应该添加表示单个零件的类了.单个零件的类名显然应该为Part.不过,当你已拥有一个Parts类时,引入Part类会让交谈变得很困难.当同样的这个名字已经用于指代单个的Parts对象时,使用"parts"一词来指代一堆的Part对象很容易让人感到困惑.不过,前面的措辞说明了一种会顺带引起交流问题的技术.当在讨论Part和Parts时,你可以在类名之后带上"objec

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

8.4 组合成Bicycle 面向对象设计实践指南:Ruby语言描述 下面的代码展示了Bicycle使用组合的情况.它展示了Bicycle.Parts.PartsFactory,以及针对公路和山地自行车的设置数组. Bicycle有一个Parts,而Parts依次有一个Part对象集合.Parts和Part都可以以类形式存在,但包含它们的对象会把它们当成角色.Parts是一个扮演Parts角色的类,它实现了spares.而Part的角色则由OpenStruct扮演,它会实现name.descri