我是 Go 新手,我想做的第一件事就是将我的小标记页面生成库移植到 Go。主要的实现是在 Ruby 中,它的设计非常“经典的面向对象”(至少我从业余程序员的角度理解 OO)。它模拟了我如何看待标记文档类型之间的关系:
Page
/ \
HTML Page Wiki Page
/ \
HTML 5 Page XHTML Page
对于一个小项目,我可能会做这样的事情(翻译成我现在想要的 Go):
p := dsts.NewHtml5Page()
p.Title = "A Great Title"
p.AddStyle("default.css")
p.AddScript("site_wide.js")
p.Add("<p>A paragraph</p>")
fmt.Println(p) // Output a valid HTML 5 page corresponding to the above
对于较大的项目,例如一个名为“Egg Sample”的网站,我将现有页面类型之一子类化,创建更深层次的层次结构:
HTML 5 Page
|
Egg Sample Page
/ | \
ES Store Page ES Blog Page ES Forum Page
这非常适合经典的面向对象设计:子类可以免费获得很多,它们只关注与父类不同的少数部分。例如,EggSamplePage 可以添加一些在所有 Egg Sample 页面中通用的菜单和页脚。
然而,Go 没有类型层次结构的概念:没有类,也没有类型继承。也没有动态的方法调度(在我看来,从上面可以看出;Go 类型HtmlPage
不是“一种”Go 类型Page
)。
Go 确实提供:
- 嵌入
- 接口
似乎这两个工具应该足以得到我想要的东西,但是在几次错误的开始之后,我感到很难过和沮丧。我的猜测是我想错了,我希望有人可以为我指出正确的方向,以了解如何做到这一点。
这是我遇到的一个具体的、真实的问题,因此,欢迎任何关于在不解决更广泛问题的情况下解决我的特定问题的建议。但我希望答案的形式是“通过以某种方式组合结构、嵌入和接口,您可以轻松获得所需的行为”,而不是回避它。我认为许多从经典 OO 语言过渡到 Go 的新手可能会经历类似的困惑时期。
通常我会在这里展示我损坏的代码,但我有几个版本,每个版本都有自己的问题,我不认为包括它们实际上会增加我的问题的清晰度,这个问题已经很长了。如果结果看起来有用,我当然会添加代码。
我做过的事情:
- 阅读大部分Go 常见问题解答(尤其是看起来相关的部分)
- 阅读大量有效围棋(尤其是似乎相关的部分)
- 使用多种搜索词组合在 Google 上搜索
- 阅读有关golang-nuts 的各种帖子
- 编写了很多不足的 Go 代码
- 查看Go 标准库源代码中的任何看起来相似的示例
要更明确地说明我在寻找什么:
我想学习惯用的 Go 方式来处理这样的层次结构。我更有效的尝试之一似乎最不像 Go:
type page struct { Title string content bytes.Buffer openPage func() string closePage func() string openBody func() string closeBody func() string }
这让我很接近,但不是一直。我现在的观点是,学习 Go 程序员在这种情况下使用的习语似乎是一个失败的机会。
我想尽可能地干(“不要重复自己”);
text/template
当每个模板的大部分内容与其他模板相同时,我不希望每种类型的页面都有单独的页面。我废弃的一个实现就是这样工作的,但是一旦我得到一个更复杂的页面类型层次结构,它似乎就会变得难以管理,如上所述。我希望能够拥有一个核心库包,它可以按原样用于它支持的类型(例如
html5Page
和xhtmlPage
),并且可以如上所述进行扩展,而无需直接复制和编辑库。(例如,在经典的 OO 中,我扩展/子类化 Html5Page 并进行一些调整。)我目前的尝试似乎不太适合这一点。
我希望正确的答案不需要太多代码来解释 Go 的思考方式。
更新:根据到目前为止的评论和答案,看来我离得不远了。我的问题一定比我想象的少一些面向设计的问题,而且更多的是关于我是如何做事的。所以这就是我正在使用的:
type page struct {
Title string
content bytes.Buffer
}
type HtmlPage struct {
page
Encoding string
HeaderMisc string
styles []string
scripts []string
}
type Html5Page struct {
HtmlPage
}
type XhtmlPage struct {
HtmlPage
Doctype string
}
type pageStringer interface {
openPage() string
openBody() string
contentStr() string
closeBody() string
closePage() string
}
type htmlStringer interface {
pageStringer
openHead() string
titleStr() string
stylesStr() string
scriptsStr() string
contentTypeStr() string
}
func PageString(p pageStringer) string {
return headerString(p) + p.contentStr() + footerString(p)
}
func headerString(p pageStringer) string {
return p.openPage() + p.openBody()
}
func HtmlPageString(p htmlStringer) string {
return htmlHeaderString(p) + p.contentStr() + footerString(p)
}
func htmlHeaderString(p htmlStringer) string {
return p.openPage() +
p.openHead() + p.titleStr() + p.stylesStr() + p.scriptsStr() + p.con tentTypeStr() +
p.openBody()
}
这可行,但它有几个问题:
- 感觉真的很尴尬
- 我在重复自己
- 这可能是不可能的,但理想情况下,我希望所有 Page 类型都有一个
String()
做正确事情的方法,而不是必须使用一个函数。
我强烈怀疑我做错了什么,并且有一些 Go 习语可以让这变得更好。
我想要一种做正确事情的String()
方法,但是
func (p *page) String( string {
return p.headerString() + p.contentStr() + p.footerString()
}
page
即使通过使用,也将始终使用这些方法HtmlPage
,因为除了接口之外在任何地方都缺乏动态调度。
使用我当前的基于界面的页面生成,我不仅不能只做fmt.Println(p)
(p
某种页面在哪里),而且我必须专门在 和 之间进行fmt.Println(dsts.PageString(p))
选择fmt.Println(dsts.HtmlPageString(p))
。这感觉很不对劲。
而且我在PageString()
/HtmlPageString()
之间和headerString()
/之间笨拙地复制代码htmlHeaderString()
。
所以我觉得我仍然在遭受设计问题,因为在某种程度上仍然在思考 Ruby 或 Java 而不是 Go。我希望有一种直接且惯用的 Go 方法来构建一个库,该库具有我所描述的客户端界面。