The trick is an X__this
pointer to the top of your inheritance chain stored at the bottom of the inheritance hierarchy.
package main
import (
"fmt"
"os"
"path/filepath"
)
type path string
type PlatformDirs interface {
UserCacheDir() string
UserCachePath() path
isPlatformDirs()
}
type PlatformDirsImpl struct {
X__this PlatformDirs // topmost impl to dyn dispatch to
appname string
appauthor string
}
func NewPlatformDirs(appname string, appauthor string) *PlatformDirsImpl {
p := &PlatformDirsImpl{
appname: appname,
appauthor: appauthor,
}
p.X__this = p
return p
}
func (p *PlatformDirsImpl) UserCacheDir() string {
panic("abstract")
}
func (p *PlatformDirsImpl) UserCachePath() path {
return path(p.X__this.(PlatformDirs).UserCacheDir())
}
func (p *PlatformDirsImpl) isPlatformDirs() {}
type Windows interface {
PlatformDirs
isWindows()
}
type WindowsImpl struct {
PlatformDirsImpl
}
func NewWindows(appname string, appauthor string) *WindowsImpl {
w := &WindowsImpl{*NewPlatformDirs(appname, appauthor)}
w.X__this = w
return w
}
func (p *WindowsImpl) UserCacheDir() string {
d, err := os.UserHomeDir()
if err != nil {
panic(err)
}
return filepath.Join(d, "the-cache-folder", p.appauthor, p.appname)
}
func (p *WindowsImpl) isWindows() {}
func main() {
w := NewWindows("MyApp", "MyCompany")
fmt.Println(w.UserCachePath())
// Output: /root/the-cache-folder/MyCompany/MyApp
}
https://go.dev/play/p/a3ijyB9h77-
X__this
can be any implementation of the root interface (which every child class will be) and then the parent class can call all child methods (which can be overridden ) on that `Xthis` pointer. I think that's cool. There are a few caveats though:
- You have to store an interface pointer which has a size and performance cost
- This requires parallel interface and struct hierarchies that intertwine
- This requires the user to make sure they don't do unexpected things with the returned instance's pointer
- You have to manually set the
X__this
pointer to the topmost implementation when you construct or compose it -
X__this
must be public (hence theX__
prefix) if you want to extend the class outside the current package - The
ThingImpl
struct must be public so it is embedded publicly in child classes
But you finally get dynamic dispatch on child-overridden methods!
Top comments (0)