---------------------------------------------------------------------
package main
import (
"fmt"
)
// our state machine interface, it may consist of one or many methods
// which behaviour depends on a particular state
type StateInterface interface {
Test()
}
// the state itself may contain some data, it is an interface also O_o
// NOTE: there is no need to specify any *State methods
type State struct {
StateInterface
name string
counter int
}
// let's define a bunch of different states
type StateA State
func (self *StateA) Test() {
fmt.Println("state A")
self.counter++
if self.counter > 1 {
// change state
self.StateInterface = (*StateB)(self)
}
}
type StateB State
func (self *StateB) Test() {
fmt.Println("state B")
}
type StateC State
func (self *StateC) Test() {
fmt.Printf("state C, hello %s\n", self.name)
// change state
self.StateInterface = (*StateA)(self)
}
func main() {
// initial setup
s := &State{nil, "nsf", 0}
s.StateInterface = (*StateC)(s)
for i := 0; i < 5; i++ {
// we can call directly StateInterface methods on *State
s.Test()
}
}
---------------------------------------------------------------------
OUTPUT:
[nsf @ ~]$ ./8.out
state C, hello nsf
state A
state A
state B
state B
---------------------------------------------------------------------
Well it's sort of a thing that some people wanted in C++ for a long
time. An ability to change the vtable directly. For example that way we
can change the whole behaviour or its part of a certain "class". And it
looks like a code that supports it is very straightforward.
But I'm totally unaware what's happening underneath, probably there are
some extra pointers involved in case if we have more than one interface
(aka customizable behaviour) in such a "class".
Anyway, what do you think?
I find myself doing state machines using the program counter of a
goroutine to hold the actual state combined with labels and goto.
func main() {
go stateMachine()
// other stuff
}
func stateMachine() {
// init
s0:
if foo {
goto s2
}
goto s3
s2:
// more s2 stuff
s3:
// s3 stuff
}
an idiom i've been
finding useful is where i want to create an object A
that satisfies a particular interface X and i already have
another object B that provides a superset
of that interface. A can inherit all of B's X methods
without making any more public, while retaining
the capacity to use B's extra methods. i do this by having
two instance variables both pointing to B; one
is the desired interface type (unnamed) and one
the concrete instance of B.
as an example, in my experimental graphics package,
there's an interface that lets an object draw itself:
// slightly simplified version
type Item interface {
Draw(dst draw.Image, clip draw.Rectangle)
Bbox() draw.Rectangle
}
there's a low-level object that implements Item, and can display an image,
but allows direct access to its fields and lacks for
some convenience methods.
type ImageItem struct {
Image draw.Image
Opaque bool
R draw.Rectangle
}
to create a higher level object, layered onto ImageItem,
i can do:
type MyItem struct {
Item
img ImageItem
// ... and other fields
}
func NewMyItem() *MyItem {
i := new(MyItem)
i.img.Image = image.NewRGBA(...)
i.img.Opaque = true
i.R = draw.Rect(...)
i.Item = &i.img
return i
}
in this way, i can choose exactly which
methods of any object to inherit.
it seems to work well, and it doesn't violate
any encapsulation properties - i could always
get the same effect simply by adding pass-through
methods to MyItem for all methods in Item.
> For a state machine I think you can leverage closures and message
> passing to get something fairly expressive.
Go's goroutines have some memory overhead (4kb per goroutine, correct
me if I'm wrong). So it's not really a good solution if you have a lot
of small state machines (e.g. state of the game unit AI in a MMORPG
game). But in the MMO case the virtual function tables isn't a good
solution probably also. :D
Anyway, thanks for a suggestion. Maybe in some cases it's ok to use
goroutines and channels.
func() {
for {
out <- fsm.Run(<-in)
}
}
when you wanted to use it as a server.
> What does the interface approach offer over a closure-based one?
Nothing. You've written the same thing as I did, just using different
language form. Interface after all it's somewhat a table with function
pointers (well, the details are more complicated, but the meaning is
roughly the same). You're on the other hand using function pointers
directly, there is no so much difference.
This is all mostly about syntax. I'm sure I could use state "enum" and
switch statement as well.