3

I'm working on a small game/simulator, written in GoLang, in which there will be potentially hundreds of abilities. For each player, they will have between 1 and 3 abilities. I'll have these stored with either strings or Ids. What is the best way to instantiate these abilities. Normally I'd use a factory class, but with as many as I'm talking about, I'm not sure that's the best way.

Jayson Bailey
  • 994
  • 10
  • 23
  • using empty struct for each of the abilities could be interesting (an empty struct has a size of zero, an array of empty struct has a size of 0: http://dave.cheney.net/2014/03/25/the-empty-struct, and http://stackoverflow.com/a/22627240/6309) – VonC Jun 10 '14 at 05:15
  • 2
    There are no classes in Go, and without understanding what you're trying to do (beyond create hundreds of something-or-others, which is a means to an end, rather than an end) it's impossible to answer the question about what pattern is most appropriate. – Paul Hankin Jun 10 '14 at 13:02
  • @Anonymous: kinda rude. Help them ask the right question rather than belittle the one they asked. A better summary would be: "How to scale or replace the factory pattern for hundreds of simple types?" – deft_code Jun 11 '14 at 18:43
  • @JaysonBailey: I'm confused by the sentence "I'll have these stored with either strings or Ids" stored where? why aren't they stored as interfaces? Can you clarify? – deft_code Jun 11 '14 at 18:51

1 Answers1

6

You can still use the factory pattern, it's what the encoding/gob package uses.

playground: http://play.golang.org/p/LjR4PTTCvw

For example in abilities.go you could have

type Ability interface {
    Execute()
}

var abilities = struct {
    m map[string]AbilityCtor
    sync.RWMutex
}{m: make(map[string]AbilityCtor)}

type AbilityCtor func() Ability

func Register(id string, newfunc AbilityCtor) {
    abilities.Lock()
    abilities.m[id] = newfunc
    abilities.Unlock()
}

func GetAbility(id string) (a Ability) {
    abilities.RLock()
    ctor, ok := abilities.m[id]
    abilities.RUnlock()
    if ok {
        a = ctor()
    }
    return
}

Then for each ability (in separate files probably) you could do something like :

type Fireball struct{}

func (s *Fireball) Execute() {
    fmt.Println("FIREBALL EXECUTED")
}

func init() {
    Register("Fireball", func() Ability {
        return &Fireball{}
    })
}

func main() {
    if fireball := GetAbility("Fireball"); fireball != nil { //could be nil if not found
        fireball.Execute()
    }
}
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • This can be simplified. `init()` functions are always non-concurrent. So it's safe to manipulate the `abilities` var directly without a mutex. `Register()` could still be called from concurrent goroutines after initialization, but I think in this case it's a non-issue as `Register()` is designed to be called during `init()`. – deft_code Jun 11 '14 at 20:57
  • `ctor()` is user supplied code, it should be panic safe. Use `defer abilities.Unlock()`. – deft_code Jun 11 '14 at 21:01
  • Good point about the mutex, however `defer` adds overhead that's really not needed unless you have multiple exit points. – OneOfOne Jun 11 '14 at 21:42
  • 1
    What I meant was that `ctor()` is not your code and in theory it could panic. Go does a good job of keeping panic to a minimal nuisance. It is safe to assume a function won't panic unless it specifically says it will (very rare, possibly bad style), or the code originates from the user. In those cases defer is a necessary evil. – deft_code Jun 11 '14 at 22:50
  • Switched to defer, I misunderstood your first comment :) – OneOfOne Jun 11 '14 at 23:10
  • Hmm, I think I'm getting close on this. However, when I try to call Get(GetAbility in my case), it's just hanging. http://play.golang.org/p/jke7lGLeHP – Jayson Bailey Jun 24 '14 at 20:36
  • It was my mistake, that was supposed to be `defer abilities.RUnlock()` not `defer abilities.Unlock()`, I fixed the answer and posted a link. – OneOfOne Jun 24 '14 at 21:35
  • Thanks @OneOfOne, that's perfect. I'm still reading through and trying to understand how it all works as I'm just learning golang from ruby. – Jayson Bailey Jun 24 '14 at 22:10