Ruby memoization和Null Object模式

时间:2018-05-25 18:38:18

标签: ruby

你好Rubyist,

想知道是否可以使用Ruby的memoization运算符||=(即:a || a = b,当编写a ||= b时)可以用于应该遵循{的自定义普通旧ruby类{3}}

例如,假设我有一个类:

class NoThing
    def status
       :cancelled
    end

    def expires_on
       0.days.from_now
    end

    def gateway
      ""
    end
end

我在没有班级Thing时使用。 Thing在其公共接口中具有相同的statusexpires_ongateway方法。

问题是,在@thing ||= Thing.new@thingnil的情况下,如何撰写NoThing之类的内容?

2 个答案:

答案 0 :(得分:2)

可以<{3}} 来自FalseClass并在NoThing上设置相同的运算符方法。但出于多种原因,我会犹豫不决。

与其他一些语言不同,Ruby 非常明确表示有一组非常有限的错误,falsenil。弄乱这可能会导致混乱和错误,它可能不值得你寻找的方便。

此外,Null对象模式是关于返回一个对象,该对象具有与执行某些操作的对象相同的接口,但它什么都不做。使其显示false会失败。写@thing ||= Thing.new的愿望与Null对象的欲望发生冲突。即使@thing返回Thing.new,您总是希望设置NoThing,这是Null对象的用途。如果使用ThingNoThing,使用该类的代码并不在乎。

相反,对于那些想要区分ThingNoThing的情况,我建议使用一些方法,例如#nothing?。然后设置Thing#nothing?以返回falseNoThing#nothing?以返回true。这允许您通过询问而不是通过硬编码类名来刺穿封装来区分它们。

class NoThing
  def status
     :cancelled
  end

  def expires_on
     0.days.from_now
  end

  def gateway
    ""
  end

  def nothing?
    true
  end
end

class Thing
  attr_accessor :status, :expires_on, :gateway
  def initialize(args={})
    @status = args[:status]
    @expires_on = args[:expires_on]
    @gateway = args[:gateway]
  end

  def nothing?
    false
  end
end

此外,Thing.new返回Thing以外的任何内容都是错误的形式。这给应该是简单的构造函数增加了额外的复杂性。它不应该返回nil,它应该抛出异常。

相反,请使用Factory Pattern保持ThingNoThing纯粹而简单。将工作决定是在Thing还是NoThing中返回ThingBuilderThingFactory。然后,您拨打ThingFactory.new_thing来获取ThingNoThing

class ThingFactory
  def self.new_thing(arg)
    # Just something arbitrary for example
    if arg > 5
      return Thing.new(
        status: :allgood,
        expires_on: Time.now + 12345,
        gateway: :somewhere
      )
    else
      return NoThing.new
    end
  end
end

puts ThingFactory.new_thing(4).nothing? # true
puts ThingFactory.new_thing(6).nothing? # false

然后,如果你真的需要它,工厂也可以有一个单独的类方法,它返回nil而不是NoThing,允许@thing ||= ThingFactory.new_thing_or_nil。但你不应该需要它,因为那是Null对象模式的用途。如果您确实需要它,请使用#nothing?和三元运算符。

thing = ThingFactory.new_thing(args)
@thing = thing.nothing? ? some_default : thing

答案 1 :(得分:2)

Schwerns answer已经解释了为什么你不应该尝试编写一个自定义的错误类。我只是想给你一个快速而相对简单的选择。

您可以在评估实例的 Thing NoThing 上添加方法:

class Thing
  def thing?
    true
  end
end

class NoThing
  def thing?
    false
  end
end

现在您可以通过以下方式分配@thing

@thing = Thing.new unless @thing&.thing?

这假定@thing始终是 NilClass Thing NoThing 类。

或者,您也可以覆盖 NoThing 中的Object#itself方法。然而,如果那些不期待 #itself 的不同结果的人使用,这可能会产生不必要的结果。

class NoThing
  def itself
    nil
  end
end

@thing = @thing.itself || Thing.new
# or
@thing.itself || @thing = Thing.new # exact ||= mimic