I am new to Go, and one of the first things I want to do is port my small Go library with extended pages. Primary implementation in Ruby, and this is a very âclassic object orientationâ in its design (at least since I understand OO from the point of view of an amateur programmer). It models how I see the relationship between the marked types of documents:
Page / \ HTML Page Wiki Page / \ HTML 5 Page XHTML Page
For a small project, I could do something like this (it is now translated into Go 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
For larger projects, say, for a website called "Egg Sample", I will 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 with the classic object-oriented design: subclasses get a lot for free, and they just focus on a few parts that differ from their parent class. EggSamplePage can add some menus and footers that are common to all egg sample pages, for example.
Go, however, has no concept of type hierarchy: there are no classes and there is no type inheritance . There is also no dynamic dispatch of methods (which seems to me the foregoing, the Go type HtmlPage not "kind", Go type Page ).
Go provides:
It seems that these two tools should be enough to get what I want, but after several false starts, I feel stumped and disappointed. I suppose I think this is wrong, and I hope someone can point me in the right direction on how to do it "Go."
This is a specific, real problem that I encountered, and any suggestions for solving my specific problem without solving a broader issue are welcome. But I hope that the answer will be in the form of "by combining structures, embeddings and interfaces in such a way that you can easily have the behavior you want," rather than bypassing this. I think that many Go beginners are transitioning from the classic OO languages, probably going through a similar period of confusion.
I usually showed my broken code here, but I have several versions, each with its own problems, and I donât think that including them will actually add clarity to my question, which is already quite long. Of course, I will add code if it proves useful.
What I've done:
To be more explicit regarding what I'm looking for:
I want to study the idiomatic approach to such hierarchies. One of my more effective attempts seems least like Go:
type page struct { Title string content bytes.Buffer openPage func() string closePage func() string openBody func() string closeBody func() string }
It brought me closer, but not completely. My point now is that it seems like an unfortunate opportunity to study idioms. Go programmers use in such situations.
I want to be as dry (Don't Repeat Yourself) as reasonable; I do not want a separate text/template for each page type when most of each template is identical to the others. One of my discarded implementations works this way, but it seems that it will become unmanageable as soon as I get a more complex hierarchy of page types, as described above.
I would like to have a base library package that can be used as-for supported types (for example, html5Page and xhtmlPage ) and is expanded as described above without resorting to help for copying and editing the library directly. (In classic OO, I extend / subclass Html5Page and make a few settings, for example.) My current attempts, it seems, have not succumbed to this very well.
I expect that the correct answer will not need a lot of code to explain the "Way" of thinking about it.
Update: Based on the comments and answers so far, it seems I was not so far away. My problems should be a little less design-oriented than I thought, and a little more about how I do things. So here is 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:
- Discomfort
- I repeat
- This may not be possible, but ideally I would like all page types to have a
String() method that does the right thing, instead of using a function.
I strongly suspect that I am doing something wrong and that there are Go idioms that could do it better.
I would 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 Page methods even when used via HtmlPage due to the lack of dynamic HtmlPage anywhere except interfaces.
With my current generation of pages based on interfaces, I canât just do just 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)) , This is very bad.
And I'm embarrassingly duplicating code between PageString() / HtmlPageString() and between headerString() / htmlHeaderString() .
So, I feel like I'm still having design issues, because to some extent I'm still thinking about Ruby or Java, not Go. I hope that there will be a simple and idiomatic approach to building a library that has something like the client interface that I described.