我已经多次重构我的树包,但没有找到我满意的解决方案,所以我想要一些关于最佳继续进行的建议。
我试图将问题简化为它的本质,并制作了一个由节点组成的树的简单示例。所有节点都有一组通用功能(在示例中表示为打开/关闭状态)。此外,还有几种类型的节点,每一种都有专门的行为(在示例中表示为实现 EditorInterface 并具有可见/隐藏状态的可编辑节点)。
在我的示例中,我们尝试满足所需的行为 - 可以打开任何节点,如果它是可编辑的,则在打开时应该使编辑器可见。
我的示例定义了两种类型的节点,文件夹和文档。文档是可编辑的。
我的直觉是为节点定义一个结构,并将通用功能包括为成员和方法。然后为文件夹和文档定义结构,在每个结构中嵌入一个匿名节点结构。
但是,这会导致一个问题,我的第一个示例将突出显示该问题。我创建了一个失败的简单测试:
示例 1:https://play.golang.org/p/V6UT19zVVU
// In this example the test fails because we're unable to access the interface in SetNodeState.
package main
import "testing"
func TestTree(t *testing.T) {
n := getTestNode()
n.SetNodeState(true)
if !n.(*document).visible {
t.Error("document is not visible")
}
}
func getTestNode() NodeInterface {
doc := &document{node: &node{}, content: "foo"}
folder := &folder{node: &node{children: []NodeInterface{doc}}, color: 123}
return folder.children[0]
}
type NodeInterface interface {
SetNodeState(state bool)
}
type EditorInterface interface {
SetEditState(state bool)
}
type node struct {
open bool
parent NodeInterface
children []NodeInterface
}
func (n *node) SetNodeState(state bool) {
n.open = state
// TODO: obviously this isn't possible.
//if e, ok := n.(EditorInterface); ok {
// e.SetEditState(state)
//}
}
type folder struct {
*node
color int
}
var _ NodeInterface = (*folder)(nil)
type document struct {
*node
visible bool
content string
}
var _ NodeInterface = (*document)(nil)
var _ EditorInterface = (*document)(nil)
func (d *document) SetEditState(state bool) {
d.visible = state
}
我曾多次尝试重构它以实现所需的行为,但没有一种方法让我满意。我不会将它们全部粘贴到问题中,但我已经创建了 Go playground 链接:
示例 2:https://play.golang.org/p/kyG-sRu6z- 在此示例中,测试通过,因为我们将接口(interface)添加为嵌入式结构的“self”成员。这似乎是一个令人讨厌的拼凑。
示例 3:https://play.golang.org/p/Sr5qhLn102 在此示例中,我们将 SetNodeState 移至接受接口(interface)的函数。这样做的缺点是我们无法访问嵌入式结构,因此所有成员都需要在接口(interface)上公开 getter 和 setter。这使界面变得不必要地复杂。
示例 4:https://play.golang.org/p/P5E1kf4dqj 在此示例中,我们提供了一个 getter 来返回整个嵌入式结构,我们在 SetNodeState 函数中使用了它。这似乎又是一场令人讨厌的混战。
示例 5:https://play.golang.org/p/HMH-Y_RstV 在此示例中,我们将接口(interface)作为参数传递给需要它的每个方法。同样,这感觉不对。
示例 6:https://play.golang.org/p/de0iwQ9gGY 在此示例中,我们删除了 NodeInterface,并从基本结构和实现 ItemInterface 的对象构造节点。这可能是这些示例中问题最少的,但仍然让我想要一个更好的解决方案。
也许有人可以提出更好的解决方案?
最佳答案
在这里,我让文档节点重新实现 SetNodeState,并使用 d.node.SetNodeState 来更新节点的状态;在非 Go-y 术语中,我会将特定于类的代码下推到子类,like this :
package main
import "testing"
func main() {
tests := []testing.InternalTest{{"TestTree", TestTree}}
matchAll := func(t string, pat string) (bool, error) { return true, nil }
testing.Main(matchAll, tests, nil, nil)
}
func TestTree(t *testing.T) {
n := getTestNode()
n.SetNodeState(true)
if !n.(*document).visible {
t.Error("document is not visible")
}
}
func getTestNode() NodeInterface {
doc := &document{node: &node{}, content: "foo"}
folder := &folder{node: &node{children: []NodeInterface{doc}}, color: 123}
return folder.children[0]
}
type NodeInterface interface {
SetNodeState(state bool)
}
type node struct {
open bool
parent NodeInterface
children []NodeInterface
}
func (n *node) SetNodeState(state bool) {
n.open = state
}
type folder struct {
*node
color int
}
var _ NodeInterface = (*folder)(nil)
type document struct {
*node
visible bool
content string
}
func (d *document) SetNodeState(state bool) {
d.node.SetNodeState(state)
d.SetEditState(state)
}
func (d *document) SetEditState(state bool) {
d.visible = state
}
这还可以让您编写适用于任何节点的通用方法without referring to specific node types ,您可能会发现它比 node 方法具有针对特定类型的类型断言的方法更清晰。
(反过来,这会让你创建一个公共(public) Node/NodeInterface 并将它们保存在与特定节点类型不同的包中,因为特定类型只会取决于一般类型,而不是相反(记忆两个 Go 包 can't both depend on each other )。但是将 node 类型与特定节点类型放在一个包中似乎是合理的做。)
如果上述方法不适用,您的第三个示例(具有采用接口(interface)的函数)之类的方法可能会有所帮助。为了缩短一点,该接口(interface)可能能够提供 getNode() *node 而不是 setOpen、appendChild 等,具体取决于关于具体情况。
Go stdlib 导出采用接口(interface)的函数,例如,io.ReadFull(r, buf) 而不是 Reader 具有 ReadFull(buf ) 方法。我怀疑在 C++ 中,将代码放在一个裸函数而不是一个方法中会被认为是不好的形式,但这在 Go 中是一种常见的做法。
因此:有时您可以通过(重新)实现特定类型的方法来获得面向对象的行为;如果不能,接受接口(interface)的函数是惯用的。
关于go - 在 Go 中正确获取多态代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33981867/
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit