如何解决使用可重用代码创建 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_missing
和 build
提取到基类或模块中不断向我抛出错误,例如:
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 举报,一经查实,本站将立刻删除。