I'm new to Go, and one of the first things I want to do is to port my little marked-up-page-generation library to Go. The primary implementation is in Ruby, and it is very much "classical object orientation" in its design (at least as I understand OO from an amateur programmer's perspective). It models how I see the relationship between marked-up document types:
Page
/ \
HTML Page Wiki Page
/ \
HTML 5 Page XHTML Page
For a small project, I might do something like this (translated to the Go I now want):
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
For larger projects, say for a website called "Egg Sample", I subclass one of the existing Page types, creating a deeper hierarchy:
HTML 5 Page
|
Egg Sample Page
/ | \
ES Store Page ES Blog Page ES Forum Page
This fits well into classical object-oriented design: subclasses get a lot for free, and they just focus on the few parts that are different from their parent class. EggSamplePage can add some menus and footers that are common across all Egg Sample pages, for example.
Go, however, does not have a concept of a hierarchy of types: there are no classes and there is no type inheritance. There's also no dynamic dispatch of methods (which seems to me to follow from the above; a Go type HtmlPage
is not a "kind of" Go type Page
).
Go does provide:
It seems those two tools should be enough to get what I want, but after several false starts, I'm feeling stumped and frustrated. My guess is that I'm thinking about it wrong, and I'm hoping someone can point me in the right direction for how to do this the "Go way".
This is a specific, real problem I'm having, and as such, any suggestions about solving my particular problem without addressing the broader question are welcome. But I'm hoping the answer will be in the form of "by combining structures, embedding, and interfaces in such-and-such a manner, you can easily have the behavior you want" rather than something that sidesteps that. I think many newcomers to Go transitioning from classical-OO languages likely go through a similar period of confusion.
Normally I would show my broken code here, but I've got several versions, each with their own problems, and I don't imagine including them will actually add any clarity to my question, which is already getting quite long. I will of course add code if it turns out to seem useful.
Things I've done:
To be a little more explicit about what I'm looking for:
I want to learn the idiomatic Go way of dealing with hierarchies like this. One of my more effective attempts seems the least Go-like:
type page struct {
Title string
content bytes.Buffer
openPage func() string
closePage func() string
openBody func() string
closeBody func() string
}
This got me close, but not all the way. My point right now is that it seems like a failed opportunity to learn the idioms Go programmers use in situations like this.
I want to be as DRY ("Don't Repeat Yourself") as is reasonable; I don't want a separate text/template
for each type of page when so much of each template is identical to others. One of my discarded implementations works this way, but it seems it would become unmanageable once I get a more complex hierarchy of page types as outlined above.
I'd like to be able to have a core library package that is usable as-is for the types that it supports (e.g. html5Page
and xhtmlPage
), and is extensible as outlined above without resorting to copying and editing the library directly. (In classical OO, I extend/subclass Html5Page and make a few tweaks, for example.) My current attempts haven't seemed to lend themselves to this very well.
I expect the correct answer won't need much code to explain the Go way of thinking about this.
Update: Based on the comments and answers so far, it seems I wasn't so far off. My problems must be a little less generally design-oriented than I thought, and a little more about exactly how I'm doing things. So here's what I'm working with:
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()
}
This works, but it has several problems:
String()
method that does the right thing, rather than having to use a function.I strongly suspect that I'm doing something wrong and that there are Go idioms that could make this better.
I'd like to have a String()
method that does the right thing, but
func (p *page) String( string {
return p.headerString() + p.contentStr() + p.footerString()
}
will always use the page
methods even when used through an HtmlPage
, due to lack of dynamic dispatch anywhere but with interfaces.
With my current interface-based page generation, not only do I not get to just do fmt.Println(p)
(where p
is some kind of Page), but I have to specifically choose between fmt.Println(dsts.PageString(p))
and fmt.Println(dsts.HtmlPageString(p))
. That feels very wrong.
And I'm awkwardly duplicating code between PageString()
/ HtmlPageString()
and between headerString()
/ htmlHeaderString()
.
So I feel like I'm still suffering design issues as a result of to some extent still thinking in Ruby or Java rather than in Go. I'm hoping there's a straightforward and idiomatic Go way to build a library that has something like the client interface I've described.
I seem to have come up with a workable solution, at least for my current task. After reading all of the advice here and talking to a friend (who doesn't know Go, but has other experience trying to model apparently hierarchical relationships without language support for type inheritance) who said "I ask myself 'What else is it? Yes, it's a hierarchy, but what else is it, and how can I model that?'", I sat down and rewrote my requirements:
I want a library with a client interface with a flow something like this:
Instantiate a page creation object, likely specifiying the format it will generate. E.g.:
p := NewHtml5Page()
Optionally set properties and add content. E.g.:
p.Title = "FAQ"
p.AddScript("default.css")
p.Add("<h1>FAQ</h1>\n")
Generate the page. E.g.:
p.String()
And the tricky part: Make it extensible, such that a website named Egg Sample could easily leverage the library to make new formats based on existing ones, which themselves can form the basis of further sub-formats. E.g.:
p := NewEggSamplePage()
p2 := NewEggSampleForumPage()
Thinking about how to model that in Go, I decided that the clients really don't need a type hierarchy: they never need to treat an EggSampleForumPage
as an EggSamplePage
or an EggSamplePage
as an Html5Page
. Rather, it seemed to boil down to wanting my "subclasses" to each have certain points in the page where they add content or occasionally have different content from their "superclass". So it's not a question of behavior, but one of data.
That's when something clicked for me: Go doesn't have dynamic dispatch of methods, but if a "subtype" (a type that embeds a "supertype") changes a data field, methods on the "supertype" do see that change. (This is what I was working with in the very un-Go-like attempt shown in my question, using function pointers rather than methods.) Here's an excerpt of what I ended up with, demonstrating the new design:
type Page struct {
preContent string
content bytes.Buffer
postContent string
}
type HtmlPage struct {
Page
Title string
Encoding string
HeadExtras string
// Exported, but meant as "protected" fields, to be optionally modified by
// "subclasses" outside of this package
DocTop string
HeadTop string
HeadBottom string
BodyTop string
BodyAttrs string
BodyBottom string
DocBottom string
styles []string
scripts []string
}
type Html5Page struct {
*HtmlPage
}
type XhtmlPage struct {
*HtmlPage
Doctype string
}
func (p *Page) String() string {
return p.preContent + p.content.String() + p.postContent
}
func (p *HtmlPage) String() string {
p.preContent = p.DocTop + p.HeadTop +
p.titleStr() + p.stylesStr() + p.scriptsStr() + p.contentTypeStr() +
p.HeadExtras + p.HeadBottom + p.BodyTop
p.postContent = p.BodyBottom + p.DocBottom
return p.Page.String()
}
func NewHtmlPage() *HtmlPage {
p := new(HtmlPage)
p.DocTop = "<html>\n"
p.HeadTop = " <head>\n"
p.HeadBottom = " </head>\n"
p.BodyTop = "<body>\n"
p.BodyBottom = "</body>\n"
p.DocBottom = "</html>\n"
p.Encoding = "utf-8"
return p
}
func NewHtml5Page() *Html5Page {
p := new(Html5Page)
p.HtmlPage = NewHtmlPage()
p.DocTop = "<!DOCTYPE html>\n<html>\n"
return p
}
While it could perhaps use some cleaning up, it was extremely easy to write once I had the idea, it works perfectly (as far as I can tell), it doesn't make me cringe or feel like I'm fighting the language constructs, and I even get to implement fmt.Stringer
like I wanted to. I've successfully generated both HTML5 and XHTML pages with my desired interface, as well as "subclassed" Html5Page
from client code and used the new type.
I consider this a success, even if it doesn't provide a clear and universal answer to the question of modeling hierarchies in Go.