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.
Asked
Active
Viewed 1,445 times
3
-
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
-
2There 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 Answers
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
-
1What 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
-
-
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