What is Composition?
- 组合是指将不同的部分结合成一个整体的行为。使用面向对象的组合技术,可以将简单的、独立的对象组合成更大更复杂的整体。
- 从本质上讲,参与组合的那些对象都很小,他们在结构上都是独立的。这使得他们能够无缝低转换为可插入、可互换的组件。
重构
创建零件
1 | class Bicycle |
组装
此时size
能正常响应,但是不能执行数组之间的加法会导致问题. 尽管+
连接的是 Parts
对象,但是+
返回的对象是 Array
实例。Array
并不知道如何响应spares
1 | road_bike_parts = Parts.new([chain, road_tire, tape]) |
让Parts对象更像一个数组
- The Forwardable module Forwardable is a module that can be used to add behavior to all the instances of a given class. This module is included to the singleton class using the extend keyword in order to add methods at class-level (to keep it simple).
- The Forwardable#def_delegator method allows an object to forward a message to a defined receiver.
- The first argument correspond to the receiver of the message forwarding.
- The second argument is the message to forward.
- And finally the third argument is an alias of the message.
- The def_delegators method
- The delegate method The delegate method accepts a hash as argument where:
- the key is one or more messages
- the value is the receiver of the messages defined as key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# in forwardable.rb
require 'forwardable'
class Computer
attr :cores, :screens
extend Forwardable
delegate %I[size] => :@cores,
%I[length] => :@screens
def initialize
@cores = (1..8).to_a
@screens = [1, 2]
end
end
macrosoft = Computer.new
puts "Cores: #{macrosoft.size}"
puts "Screens: #{macrosoft.length}"
The 2 main differences with the def_delegator method is that it takes a set of methods to forward and the methods cannot be aliased 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60require 'forwardable'
class Bicycle
attr_reader :size, :parts
def initialize(args = {})
@size = args[:size]
@parts = args[:parts]
end
def spares
parts.spares
end
end
class Parts
extend Forwardable
def_delegators :@parts, :size, :each
include Enumerable
def initialize(parts)
@parts = parts
end
def spares
select {|part| part.needs_spare}
end
end
class Part
attr_reader :name, :description, :needs_spare
def initialize(args)
@name = args[:name]
@description = args[:description]
@needs_spare = args.fetch(:needs_spare,true)
end
end
chain = Part.new(name: "chain", description: '10-speed')
road_tire = Part.new(name: "tire_size", description: '23')
tape = Part.new(name: "tape_color", description: 'red')
mountain_tire = Part.new(name: "tire_size", description: '2.1')
rear_shock = Part.new(name: "rear_shock", description: 'Fox')
front_shock = Part.new(name: 'front_shock', description: 'Manitou', needs_spare: false)
mountain_bike = Bicycle.new(
size: 'L',
parts: Parts.new([chain, mountain_tire, front_shock, rear_shock])
)
road_bike = Bicycle.new(
size: 'L',
parts: Parts.new([chain, road_tire, tape])
)
p mountain_bike.size
p road_bike.size
创建零件工厂
对象如何创建的知识,最好放在工厂里面
。这样你就只需要一个说明书,就能创建对象。
1 | road_config = [['chain','10-speed'], |