Modeling a hierarchy of related things without language support for type hierarchies

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:

  • The attachment
  • Interfaces

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.

+4
source share
5 answers

I seem to have come up with a practical solution, at least for my current task. After reading all the tips here and talking to a friend (who does not know Go, but has a different experience trying to model apparently hierarchical relationships without language support for type inheritance), who said: "I ask myself:" What else? "This is a hierarchy, but what else is it, and how can I model it?", I sat down and rewrote my requirements:

I need a client interface library with a stream like this:

  • Create an instance of the page creation object, probably by defining the format that it will generate. For instance:.

     p := NewHtml5Page() 
  • Optionally set properties and add content. For instance:.

     p.Title = "FAQ" p.AddScript("default.css") p.Add("<h1>FAQ</h1>\n") 
  • Create a page. For instance:.

     p.String() 
  • And the tricky part: make it extensible so that a site called Egg Sample can easily use the library to create new formats based on existing ones, which themselves can become the basis for further sub-formats. For instance:.

     p := NewEggSamplePage() p2 := NewEggSampleForumPage() 

Thinking about how to simulate this in Go, I decided that clients really didn’t need a type hierarchy: they never need to consider EggSampleForumPage as EggSamplePage or EggSamplePage as Html5Page . It rather seemed to come down to the desire that my “subclasses” of each had certain items on the page where they add content or sometimes have different content from their "superclass". So this is not a matter of behavior, but one of the data.

What, when something clicked for me: Go has no dynamic method submission, but if the "subtype" (the type that implements the "supertype") changes the data field, the methods in the "supertype" see that change. (This is what I worked on in the most non-Go-like attempt shown in my question using function pointers, not methods.) Here is an excerpt from what I ended up 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 } 

Although he could use some cleaning, it was very easy to write, as soon as I had an idea, it works fine (as far as I can tell), it does not make me cringe or feel like I'm fighting language constructs, and I can even implement fmt.Stringer as I wanted. I successfully created both HTML5 and XHTML pages with my desired interface, as well as "subclasses" of Html5Page from client code, and used a new type.

I consider this a success, even if it does not provide a clear and universal answer to the question of modeling hierarchies in Go.

0
source

First, a warning: deep hierarchies are painful for adaptation in all languages. Deep hierarchical structural modeling is often a trap: it intellectually satisfies at first, but ends like a nightmare.

Then Go has an attachment, which is actually a composition, but provides most of what is usually needed with (potentially multiple) inheritance.

For example, let's look at this:

 type ConnexionMysql struct { *sql.DB } type BaseMysql struct { user string password string database string } func (store *BaseMysql) DB() (ConnexionMysql, error) { db, err := sql.Open("mymysql", store.database+"/"+store.user+"/"+store.password) return ConnexionMysql{db}, err } func (con ConnexionMysql) EtatBraldun(idBraldun uint) (*EtatBraldun, error) { row := con.QueryRow("select pv, pvmax, pa, tour, dla, faim from compte where id=?", idBraldun) // stuff return nil, err } // somewhere else: con, err := ms.bd.DB() defer con.Close() // ... somethings, err = con.EtatBraldun(id) 

As you can see, with just an attachment, I could:

  • easy to generate an instance of a subclass of ConnexionMysql
  • define and use my own functions like EtatBraldun
  • still use functions defined on *sql.DB , e.g. Close of QueryRow
  • if necessary (not present here) add fields to my subclass and use them

And I could embed several types. Or the "subtype" of my type ConnexionMysql .

In my opinion, this is a good compromise, and it helps to avoid the traps and rigidity of deep inheritance hierarchies.

I say this is a compromise because it is clear that Go is not an OOP language. The lack of an override function, as you saw, prevents the usual decision of having methods in the "superclass" that makes up the calls to the "subclasses" methods.

I understand that this can be troubling, but I'm not sure that I really miss the weight and verbosity of conventional hierarchical solutions. As I said, at first everything is fine, but painful when it becomes difficult. This is why I suggest you try Go Go:

Interfaces

Go can be used to reduce the need for inheritance. In fact, your page can be an interface (or more idiomatically multiple interfaces) and a structure:

 type pageOpenerCloser interface { openPage func() string closePage func() string openPage func() string closePage func() string } type page struct { Title string content bytes.Buffer } 

Since you cannot rely on the String() method defined in the pageOpenerCloser implementation, just call the closeBody method defined in the same implementation, you should use functions, not methods to do part of the work, which I see as composition: you should pass your pageOpenerCloser instance into Composing functions that will invoke the correct implementations.

It means

  • you are prompted to have atomic and orthogonal interface definitions Interfaces
  • determined by their (simple) actions
  • Interface functions should not call other functions in one instance
  • you are not recommended to have a deep hierarchy with intermediate levels defined by implementation / algorithms
  • large “compilation” algorithms should not be redefined or generally written more than once

I feel that it reduces clutter and helps make the Go program small and understandable.

+3
source

Inheritance combines two concepts. Polymorphism and code exchange. Go shares these concepts.

  • Polymorphism ('is') in Go is achieved through interfaces.
  • Go code exchange is achieved through implementation and functions acting on interfaces

Many people who come from OOP languages ​​forget about functions and get lost using only methods.

Because Go separates these concepts, you must think about them individually. What is the relationship between the "page" and the "egg sample page". Is this a “eat” relationship or is it a code-sharing relationship?

+3
source

I think it is impossible to answer your question satisfactorily. But let a short analogy begin from a different context, in order to avoid generally accepted ideas about programming (for example, many programmers consider OOP to be the “right way” for programming, because this is what they have been doing for many years).

Suppose you are playing a classic game called Bridge Builder. The goal of this game is to build a bridge over the columns so that the train can pass from one side to the other. Once, having mastered the game for many years, you decide that you want to try something new. Let Portal 2 say :)

You easily manage the first level, but you cannot understand how you can get to the platform from the other side on the second. So you ask a friend: “Hey, how can I post posts on Portal 2?” Your friend may look embarrassed, but he can tell you that you can pick up these boxes and put them on top of each other. So, you will immediately start collecting all the boxes that you can find in order to build your bridge on the other side of the room. Well done!

In any case, after a couple of hours you will find that Portal 2 is really disappointing (it takes a lot of time to collect blocks and the levels are really difficult). So you stop playing.

So what is wrong here? Firstly, you suggested that one technique from one game might work well in another. Secondly, you did not ask the right question. Instead of telling your friend about your problem ("how can I get to this platform there?"), You asked him how you can archive those things that you are used to in other games. If you asked another question, your friend could tell you that you can use your portal machine gun to create a red and blue portal and walk through.

It is very difficult to try porting a well-written Ruby / Java / program, etc. for go. One thing that works well in one language may not work so well in another. You did not even ask us what problem you are trying to solve. You just posted some kind of useless boilerplate code that shows some class hierarchies. You do not need to go, because Go interfaces are more flexible. (A similar analogy can be drawn between the Javascript prototype and people trying to program the OOP path in Javascript.)

At first it was hard to find good projects in Go, especially if you are used to OOP. But decisions in Go are usually smaller, more flexible, and much easier to understand. Carefully review all of these packages in the Go standard library and other external packages. For example, I think leveldb-go is much simpler and more understandable than leveldb, even if you know both languages ​​well.

+1
source

Perhaps try to solve your problem as follows:

  • Create a function that takes a minimal interface to describe the page and outputs html.
  • Model all your pages thinking of “relationships.” For example, you might have a headline structure that you insert on a page using the GetTitle () method that you type, validate and validate (not everyone has a headline, so you don't want this to be part of the “required” functionality in the interface that the function accepts.) Instead of thinking about the page hierarchy, think about how the pages are arranged together.

One thing that I usually find useful is to think of interfaces as capturing functionality and structures as capturing data.

+1
source

All Articles