Sandi Metz在来自 GORUCO 的 SOLID OOPS概念中说,Ruby中if..else
块的存在可以被视为偏离 Open-关闭原则。可以使用哪些方法来避免不紧急的if..else
条件?我尝试了以下代码:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
并发现函数重载在Ruby中不起作用。有什么其他方法可以使代码服从OCP?
我本可以去找:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
但这违反了OCP。
答案 0 :(得分:1)
你可以这样做:
class Parent
attr_reader :s
def initialize(s='')
@s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
@x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
@y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
或者,也许我忽略了你的一个曲折:
class Parent
attr_reader :x
def initialize(s='')
@x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
使用 blocks 的另一个例子,就像匿名函数一样。您可以将所需的行为传递给park()作为函数:
class Function
attr_reader :block
def initialize(&park)
@block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
答案 1 :(得分:1)
使用您当前的示例,并希望避免类型检测,我将使用Ruby的功能重新打开类,以添加Integer
和String
所需的功能:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
在更改自己的类时,这会更干净。但也许它不适合你的概念模型(它取决于Fun
代表什么,以及为什么它可以在一个方法中采用这两个不同的类)。
等同但保留Fun
课程可能是:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
作为一种观点,我不确定你会以这种方式获得多少Ruby。你正在学习的原则可能很好(我不知道),但强行应用它们“语言”可能会产生不太可读的代码,无论它是否符合善意的设计。
所以我可能在实践中做的是:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
这不符合您的原则,但是惯用Ruby并且比if
块更容易阅读和维护(其中条件可能要复杂得多,因此需要更长时间才能解析)。
答案 2 :(得分:1)
你可以像Fun那样为Fun创建处理过的类
class Fun
def park(obj)
@parker ||= Object.const_get("#{obj.class}Park").new(obj)
@parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
@park = p
end
def park
@park
end
end
class FixnumPark < Park
def park
@park += 2
end
end
class StringPark < Park
end
然后这样的事情会起作用
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("@parker")
#=> #<StringPark:0x1e04b48 @park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("@parker")
#=> #<FixnumPark:0x1e04b48 @park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
答案 3 :(得分:0)
查看is_a?方法
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
但最好不要检查类型,而是使用duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD:阅读评论后。是的,上面的两个例子都没有解决OCP问题。我就是这样做的:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
答案 4 :(得分:-1)
我理解或等同于红宝石中的操作,但你可以解释一下 你做了:
Object.const_get("#{obj.class}Park").new(obj)
在ruby中,以大写字母开头的东西是常数。以下是const_get()
如何工作的简单示例:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
当然,您也可以将参数传递给dog_class.new
:
class Dog
attr_reader :name
def initialize(name)
@name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
以下几行只是上述内容的变体:
Object.const_get("#{obj.class}Park").new(obj)
如果是obj = 'hello'
,则第一部分:
Object.const_get("#{obj.class}Park")
相当于:
Object.const_get("#{String}Park")
当String类对象被插入到字符串中时,它只是转换为字符串“String”,为您提供:
Object.const_get("StringPark")
该行检索StringPark类,为您提供:
Object.const_get("StringPark")
|
V
StringPark
然后,添加原始行的第二部分将为您提供:
StringPark.new(obj)
因为obj = 'hello'
,这相当于:
StringPark.new('hello')
Capice?