微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用可重用代码创建 Ruby 构建器对象

如何解决使用可重用代码创建 Ruby 构建器对象

我正在努力创建一些 Ruby 构建器对象,并思考如何重用 Ruby 的一些魔法来将构建器的逻辑简化为单个类/模块。我上次用这种语言跳舞已经过去了大约 10 年,所以有点生疏了。

例如,我有这个构建器:

class Person
  PROPERTIES = [:name,:age]
  attr_accessor(*PROPERTIES)

  def initialize(**kwargs)
    kwargs.each do |k,v|
      self.send("#{k}=",v) if self.respond_to?(k)
    end
  end

  def build
    output = {}
    PROPERTIES.each do |prop|
      if self.respond_to?(prop) and !self.send(prop).nil?
        value = self.send(prop)
        # if value itself is a builder,evalute it
        output[prop] = value.respond_to?(:build) ? value.build : value
      end
    end

    output
  end
  
  def method_missing(m,*args,&block)
    if m.to_s.start_with?("set_")
      mm = m.to_s.gsub("set_","")
      if PROPERTIES.include?(mm.to_sym)
        self.send("#{mm}=",*args)
        return self
      end
    end
  end
end

可以这样使用:

Person.new(name: "Joe").set_age(30).build
# => {name: "Joe",age: 30}

我希望能够将所有内容重构为一个类和/或模块,以便我可以创建多个此类构建器,这些构建器只需要定义属性并继承或包含其余部分(并可能相互扩展)。

class BuilderBase
  # define all/most relevant methods here for initialization,# builder attributes and  object construction
end

module BuilderHelper
  # possibly throw some of the methods here for better scope access
end

class Person < BuilderBase
  include BuilderHelper
  
  PROPERTIES = [:name,:age,:email,:address]
  attr_accessor(*PROPERTIES)
end

# Person.new(name: "Joe").set_age(30).set_email("joe@mail.com").set_address("NYC").build

class Server < BuilderBase
  include BuilderHelper

  PROPERTIES = [:cpu,:memory,:disk_space]
  attr_accessor(*PROPERTIES)
end

# Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").build

我已经做到了这一点:

class BuilderBase
  def initialize(**kwargs)
    kwargs.each do |k,v) if self.respond_to?(k)
    end
  end
end

class Person < BuilderBase
  PROPERTIES = [:name,:age]
  attr_accessor(*PROPERTIES)

  def build
    ...
  end
  
  def method_missing(m,&block)
    ...
  end
end

尝试将 method_missingbuild 提取到基类或模块中不断向我抛出错误,例如:

NameError: uninitialized constant BuilderHelper::PROPERTIES

OR

NameError: uninitialized constant BuilderBase::PROPERTIES

本质上,父类和 mixin 都无法访问子类的属性。对于父级来说,这是有道理的,但不确定为什么 mixin 无法读取它所包含的类中的值。这是 Ruby,我确信有一些神奇的方法可以做到这一点,但我错过了。

感谢帮助 - 谢谢!

解决方法

我将您的样品减少到所需的部分并得出:

module Mixin
  def say_mixin
    puts "Mixin: Value defined in #{self.class::VALUE}"
  end
end

class Parent
  def say_parent
    puts "Parent: Value defined in #{self.class::VALUE}"
  end
end

class Child < Parent
  include Mixin

  VALUE = "CHILD"
end


child = Child.new
child.say_mixin
child.say_parent

这是如何从父类/包含类访问位于子类/包含类中的常量。

但我不明白你为什么想要把整个 Builder 放在首位。 OpenStruct 不适合您的情况吗?

,

有趣的问题。正如@Pascal 所述,OpenStruct 可能已经满足您的需求。

不过,显式定义 setter 方法可能更简洁。用方法调用替换 PROPERTIES 常量也可能更清晰。由于我希望 build 方法返回一个完整的对象而不仅仅是一个哈希,我将它重命名为 to_h

class BuilderBase
  def self.properties(*ps)
    ps.each do |property|
      attr_reader property
      define_method :"set_#{property}" do |value|
        instance_variable_set(:"@#{property}",value)
        @hash[property] = value
        self
      end
    end
  end

  def initialize(**kwargs)
    @hash = {}
    kwargs.each do |k,v|
      self.send("set_#{k}",v) if self.respond_to?(k)
    end
  end

  def to_h
    @hash
  end
end

class Person < BuilderBase
  properties :name,:age,:email,:address
end

p Person.new(name: "Joe").set_age(30).set_email("joe@mail.com").set_address("NYC").to_h
# {:name=>"Joe",:age=>30,:email=>"joe@mail.com",:address=>"NYC"}

class Server < BuilderBase
  properties :cpu,:memory,:disk_space
end

p Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").to_h
# {:cpu=>"i9",:memory=>"32GB",:disk_space=>"1TB"}
,

我认为不需要声明 PROPERTIES,我们可以像这样创建一个 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() wait = WebDriverWait(driver,10) driver.get('yoururl') wait.until(EC.element_to_be_clickable((By.XPATH,"//span[.='BUY']"))).click() wait.until(EC.element_to_be_clickable((By.XPATH,"//div[.='100%']"))).click()

general builder

使用

class Builder
  attr_reader :build

  def initialize(clazz)
    @build = clazz.new
  end

  def self.build(clazz,&block)
    builder = Builder.new(clazz)
    builder.instance_eval(&block)
    builder.build
  end

  def set(attr,val)
    @build.send("#{attr}=",val)
    self
  end

  def method_missing(m,*args,&block)
    if @build.respond_to?("#{m}=")
      set(m,*args)
    else
      @build.send("#{m}",&block)
    end
    self
  end

  def respond_to_missing?(method_name,include_private = false)
    @build.respond_to?(method_name) || super
  end
end

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。