Single Responsibility Principle

设计是保留可变性的艺术,而非达到完美性的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Gear
def initialize(chainring, cog, rim, tire)
@chainring = chainring
@cog = cog
@rim = rim
@tire = tire
end

def ratio
@chainring / @cog.to_f # <------- 依赖数据
end


def gear_inches
@ratio * (@rim + (@tire * 2)) # <------- 不符合单一职责,依赖数据
end

end

puts Gear.new(52,11,26,1.5).gear_inches
1
2
3
4
5
6
7
8
9
10
11
12
13
class ObscuringReference
attr_reader :data

def initialize(data)
@data = data
end

def diameters
data.collect {|cell|
cell[0] + (cell[1] * 2) # <---- 依赖数据结构
}
end
end

如何确定单一职责

  1. 假设它存在意识,然后质询它
    • 齿轮先生,请问你的比率是多少? ## 靠谱的询问
    • 齿轮先生,请问你的轮胎尺寸是多少?## 荒唐可笑的询问
  2. 尝试用一句话来描述类,如果出现 and 或者 or,则说明不遵循单一职责原则
  3. 高内聚(cohesion)
    OO的设计者使用内聚来描述某个类的所有内容都与其中心目标相关联的情况。一个类有责任设计其目标。

依赖行为,不要依赖数据

  1. 实例变量被引用多次,如果需要进行调整,则需要大量重改。
  2. 将数据处理成行为,对行为的一次更改,可以作用在被引用的所有数据上。
  3. 隐藏数据结构
    方法 diameters 不仅知道如何计算直径,他还知道在哪里找到钢圈(cell[0])和轮胎(cell[1]).

解决方案

  1. 将数据结构的知识,封装在单一方法内部。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Wheel = Struct.new(:rim,:tire)
    def wheelify(data)
    data.collect {
    |cell| Wheel.new(cell[0],cell[1])
    }
    end


    def diameters
    wheels.collect {|wheel| wheel.rim * (wheel.tire * 2)}
    end
  2. 将额外的责任从方法里提取出来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def diameters
    wheels.collect {|wheel| diameter wheel}
    end

    def diameter(wheel)
    wheel.rim + (wheel.tire * 2)
    end


    def gear_inches
    ration * diameter
    end
  3. 将类里的职责隔离
    暂时缺乏足够的信息证明Wheel需要一个独立的类。有的时候,我们是否需要创建一个新的对象并不是有明确界限的。隔离起来总是不错的选择。同时要注意,不要讲无关的职责混入自己的类。

    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
    class Gear
    attr_reader :chainring, :cog, :wheel
    def initialize(chainring,cog,rim,tire)
    @chainring = chainring
    @cog = cog
    @wheel = Wheel.new(rim,tire)
    end

    def ratio
    chainring / cog.to_f
    end

    def gear_inches
    ratio * wheel.diameter
    end

    Wheel = Struct.new(:rim,:tire) do # <----- 虽然Wheel存在于Gear里不算很好的选择,但是隔离Wheel的职责是不错的选择
    def diameter
    rim + (tire * 2)
    end
    end
    end


    puts Gear.new(52,11,26,1.5).gear_inches

需求的更改

如果此时,我们需要计算轮子的周长。这正是我们一直等待的信息,因为它提供了做下一步设计决定时所需要的信息。 注意敏捷开发里设计和代码迭代互相交互的原则。

现在我们有理由将Wheel独立成一个类了。因为我们将Wheel的职责隔离过,此时独立出它是很容易的。

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
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(chainring,cog,wheel = nil)
@chainring = chainring
@cog = cog
@wheel = wheel
end

def ratio
chainring / cog.to_f
end

def gear_inches
ratio * wheel.diameter
end
end


class Wheel
attr_reader :rim,:tire
def initialize(rim,tire)
@rim = rim
@tire = tire
end

def diameter
rim + (tire * 2)
end

def circumference
diameter * Math::PI
end
end

@wheel = Wheel.new(12,1.5)
puts @wheel.circumference


puts Gear.new(52,11,@wheel).gear_inches
puts Gear.new(52,11).ratio
Donate article here