代码重构中可能提供的帮助

时间:2015-06-18 14:26:46

标签: ruby oop refactoring solid-principles

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。

5 个答案:

答案 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的功能重新打开类,以添加IntegerString所需的功能:

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?