Ruby 中的访问者模式,还是只使用块?
嘿,我已经阅读了这里关于何时/如何使用访问者模式的几篇文章,以及一些关于它的文章/章节,如果您正在遍历 AST 并且它是高度结构化的,并且您想要封装但是对于 Ruby,这似乎有点矫枉过正,因为您可以使用块来完成几乎相同的事情。
我想使用 Nokogiri 来 Pretty_print xml。作者建议我使用访问者模式,这需要我创建一个 FormatVisitor 或类似的东西,所以我可以只说“node.accept(FormatVisitor.new)”。
问题是,如果我想开始自定义 FormatVisitor 中的所有内容(假设它允许您指定节点的选项卡方式、属性的排序方式、属性的间距等),该怎么办?
- 一次,我希望节点的每个嵌套级别有 1 个选项卡,并且属性可以任意顺序
- 下一次,我希望节点有 2 个空格,并且属性按字母顺序排列
- 下一次,我希望它们具有每行有 3 个空格,有两个属性。
我有几个选项:
- 在构造函数中创建一个选项哈希 (FormatVisitor.new({:tabs => 2})
- 在构造了 Visitor 后
- 为每个新实现设置 FormatVisitor 的子类
- 或者只使用块,而不是访问者
不必构造 FormatVisitor、设置值并将其传递给 node.accept 方法,为什么不这样做:
node.pretty_print do |format|
format.tabs = 2
format.sort_attributes_by {...}
end
这与我认为的访客模式形成鲜明对比:
visitor = Class.new(FormatVisitor) do
attr_accessor :format
def pretty_print(node)
# do something with the text
@format.tabs = 2 # two tabs per nest level
@format.sort_attributes_by {...}
end
end.new
doc.children.each do |child|
child.accept(visitor)
end
也许我的访问者模式完全错误,但从我在 ruby 中读到的内容来看,这似乎有点矫枉过正。你怎么认为?无论哪种方式对我来说都很好,只是想知道你们对此有何感想。
多谢, 槊
Hey there, I have read the few posts here on when/how to use the visitor pattern, and some articles/chapters on it, and it makes sense if you are traversing an AST and it is highly structured, and you want to encapsulate the logic into a separate "visitor" object, etc. But with Ruby, it seems like overkill because you could just use blocks to do nearly the same thing.
I would like to pretty_print xml using Nokogiri. The author recommended that I use the visitor pattern, which would require I create a FormatVisitor or something similar, so I could just say "node.accept(FormatVisitor.new)".
The issue is, what if I want to start customizing all the stuff in the FormatVisitor (say it allows you to specify how nodes are tabbed, how attributes are sorted, how attributes are spaced, etc.).
- One time I want the nodes to have 1 tab for each nest level, and the attributes to be in any order
- The next time, I want the nodes to have 2 spaces, and the attributes in alphabetical order
- The next time, I want them with 3 spaces and with two attributes per line.
I have a few options:
- Create an options hash in the constructor (FormatVisitor.new({:tabs => 2})
- Set values after I have constructed the Visitor
- Subclass the FormatVisitor for each new implementation
- Or just use blocks, not the visitor
Instead of having to construct a FormatVisitor, set values, and pass it to the node.accept method, why not just do this:
node.pretty_print do |format|
format.tabs = 2
format.sort_attributes_by {...}
end
That's in contrast to what I feel like the visitor pattern would look like:
visitor = Class.new(FormatVisitor) do
attr_accessor :format
def pretty_print(node)
# do something with the text
@format.tabs = 2 # two tabs per nest level
@format.sort_attributes_by {...}
end
end.new
doc.children.each do |child|
child.accept(visitor)
end
Maybe I've got the visitor pattern all wrong, but from what I've read about it in ruby, it seems like overkill. What do you think? Either way is fine with me, just wondering what how you guys feel about it.
Thanks a lot,
Lance
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
本质上,Ruby 块是没有额外样板的访问者模式。对于简单的情况,一个块就足够了。
例如,如果您想对 Array 对象执行简单的操作,您只需使用块调用
#each
方法,而不是实现单独的 Visitor 类。然而,在某些情况下实现具体的访问者模式有一些优点:
您的实现似乎有点复杂,并且 Nokogiri 期望有一个实现
#visit
方法的 Visitor 实例,因此 Visitor 模式实际上非常适合您的特定用例。以下是访问者模式的基于类的实现:FormatVisitor 实现
#visit
方法,并使用Formatter
子类根据节点类型和其他条件格式化每个节点。这里是上面使用的 AttributesFormatter 的实现。
NodeFormatter 使用工厂模式为特定节点实例化正确的格式化程序。在本例中,我区分了文本节点、叶元素节点、带文本的元素节点和常规元素节点。每种类型都有不同的格式要求。另请注意,这并不完整,例如未考虑注释节点。
使用此类: 使用
上面的代码,您可以通过构造 NodeFromatter 的额外子类并将它们插入工厂方法来更改节点格式化行为。您可以使用
AttributesFromatter
的各种实现来控制属性的格式。只要您遵循其接口,就可以将其插入到attributes_formatter_class参数中,而无需修改任何其他内容。使用的设计模式列表:
NodeFormatter
上的类方法,您可以将它们提取到NodeFormatterFactory
中,这样会更合适。这演示了如何将几种模式组合在一起以获得您想要的灵活性。不过,如果您需要这些灵活性,则您必须做出决定。
In essence, a Ruby block is the Visitor pattern without the extra boilerplate. For trivial cases, a block is sufficient.
For example, if you want to perform a simple operation on an Array object, you would just call the
#each
method with a block instead of implementing a separate Visitor class.However, there are advantages in implementing a concrete Visitor pattern under certain cases:
Your implementation seems mildly complex, and Nokogiri expects a Visitor instance that impelment
#visit
method, so Visitor pattern would actually be a good fit in your particular use case. Here is a class based implementation of the visitor pattern:FormatVisitor implements the
#visit
method and usesFormatter
subclasses to format each node depending on node types and other conditions.Here the implementation of
AttributesFormatter
used above.NodeFormatter
s uses Factory pattern to instantiate the right formatter for a particular node. In this case I differentiated text node, leaf element node, element node with text, and regular element nodes. Each type has a different formatting requirement. Also note, that this is not complete, e.g. comment nodes are not taken into account.To use this class:
With the above code, you can change node formatting behaviors by constructing extra subclasses of
NodeFromatter
s and plug them into the factory method. You can control the formatting of attributes with various implementation of theAttributesFromatter
. As long as you adhere to its interface, you can plug it into theattributes_formatter_class
argument without modifying anything else.List of design patterns used:
NodeFormatter
, you can extract them intoNodeFormatterFactory
to be more proper.This demonstrates how you can combine a few patterns together to achieve the flexibility you desire. Although, if you need those flexibility is something you have to decide.
我会选择简单且有效的方法。我不知道细节,但你写的与访问者模式相比,看起来更简单。如果它也适合你,我会使用它。就我个人而言,我厌倦了所有这些技术,这些技术要求您创建一个巨大的相互关联的类“网络”,只是为了解决一个小问题。
有人会说,是的,但是如果您使用模式来做到这一点,那么您可以满足许多未来的需求等等。我说,现在就做有效的事情,如果需要的话,你可以在将来进行重构。在我的项目中,这种需求几乎从未出现过,但那是另一回事了。
I would go with what is simple and works. I don't know the details, but what you wrote compared with the Visitor pattern, looks simpler. If it also works for you, I would use that. Personally, I am tired with all these techniques that ask you to create a huge "network" of interelated classes, just to solve one small problem.
Some would say, yeah, but if you do it using patterns then you can cover many future needs and blah blah. I say, do now what works and if the need arises, you can refactor in the future. In my projects, that need almost never arises, but that's a different story.