Thursday, January 8, 2015

golang: Do you need a singleton? Where are my private/public class modifiers?

Intro: Singletons can be useful, but as mentioned in other people's posts they are an anti pattern and can make testing and debugging difficult. A post on stack overflow had this short nonspecific message "Just put your variables and functions at the package level." http://stackoverflow.com/questions/1823286/singleton-in-go I took this as a hint that with a little more knowledge of how go packages work, I could get a single instance of an object and have it abstracted so that modification would be difficult, if not impossible.

Let's start with three files.

src/logger/logger.go
src/chat_server/main.go
src/chat_server/sub/sub.go

logger.go

package logger

import (
     "fmt"
)

type logger struct {
    Timestamp int
}

type Togger struct {
    Timestamp int
}

var log = logger{Timestamp: 12345}
var Tog = Togger{Timestamp: 12345}

func (l *logger)writeInfoFile(s string) {
    fmt.Println(s)
}

func (l *logger)writeErrorFile(s string) {
    fmt.Println(s)
}

func Debug(s string) {
    log.writeInfoFile(s)
    fmt.Println(&log.Timestamp)
}

There is a logger object named log with a single timestamp field. The other thing to note is that the functions, variables and structs (golang's version of a class) can have an upper or lower case first letter.

"if something starts with a capital letter that means other packages (and programs) are able to see it" - http://www.golang-book.com/11/index.htm

For those us who are used to having public and private keywords in our OO language, this capital letter convention is how go restricts access. This means that Togger, Tog, and Debug() can be referred to once logger is imported in another file. While log, loger and write*() can not. This takes a little closer to our goal because we should be able to instantiate an instance of logger, and only allow specific functions to act on it.

main.go

package main

import (
    "logger"
    "chat_server/sub"
    "fmt"
)

func main() {
    logger.Debug("one")
    logger.Debug("two")
   
    sub.CheckingAddress()
   
    isPublic := logger.Togger{Timestamp: 555}
   
    fmt.Println(isPublic.Timestamp)
    fmt.Println(logger.Tog.Timestamp)
   
    // These will cause an error
//    fmt.Println(logger.log.Timestamp)
//    isPublic := logger.logger{Timestamp: 333}
}   

Here are some examples of how this works. Debug is a function that can be accessed after the import. It can refer to var log as it's within the same package. Trying to access logger.log directly is not allowed, while the analagous struct value logger.Tog can be called. Any attempt to init a new 'logger' object also fails.

There is one more quick check that I wanted to verify and that is to confirm the log object continues to be the only instance no matter where it is imported.

sub.go

package sub

import (
    "logger"
)

func CheckingAddress() {
   
    logger.Debug("In SUB")
   
}

Way back in logger.go I print out the memory address of the Timestamp field.

fmt.Println(&log.Timestamp)

After running main, the output is:

one
0x547068
two
0x547068
In SUB
0x547068
555
12345

We get the same address so it's good.

Conclusion: I was able to create a single instance of my logger struct without resorting to a singleton and learned how go packages limit access to vars and and functions.