Sylvanas Garde
Sylvanas Garde

Reputation: 27

Struct with dynamic type

I am learning Go at the moment and I write a small project with some probes which report to a internal Log. I have a basic probe and I want create new probes extending the basic probe.

I want save the objects in an array/slice LoadedProbes.

type LoadableProbe struct {
    Name   string
    Probe  Probe
    Active bool
}

var LoadableProbes []LoadableProbe

The basic probe struct is:

type ProbeModule struct {
    version   VersionStruct
    name      string
    author    string
    log       []internals.ProbeLog
    lastcall  time.Time
    active    bool
}

func (m *ProbeModule) New(name string, jconf JsonConfig) {
    // read jsonConfig 
}
func (m *ProbeModule) Exec() bool {
    // do some stuff
    return true
}

func (m *ProbeModule) String() string {
    return m.name + " from " + m.author
}

func (m *ProbeModule) GetLogCount() int {
    return len(m.log)
}
[...]

I am using this basic struct for other probes, for example:

type ShellProbe struct {
    ProbeModule
}

func (s *ShellProbe) New(name string, jconf JsonConfig) {
    s.ProbeModule.New(name, jconf)
    fmt.Println("Hello from the shell")
}

func (s *ShellProbe) Exec() bool {
    // do other stuff
    return true
}

during Init() I call the following code:

func init() {
    RegisterProbe("ShellProbe", ShellProbe{}, true)
}

func RegisterProbe(name string, probe Probe, state bool) {
    LoadableProbes = append(LoadableProbes, LoadableProbe{name, probe, state})
}

The Problem is now that I can't add the type Shellprobe the the LoadableProbe struct, which expects a Probe struct.

My idea was to use interface{} instead the Probe struct in the Loadable Probe struct. But when I call the New() method of the Probe object:

for _, p := range probes.LoadableProbes {
    probe.Probe.New(probe.Name, jconf)
}

But I got the error: p.Probe.New undefined (type interface {} is interface with no methods)

how can I solve this problem?

Upvotes: 1

Views: 127

Answers (2)

jrefior
jrefior

Reputation: 4422

If you will have common data fields in each probe type, you might consider using Probe as a concrete base type defining your base data fields and base methods, and using a new ProbeInterface interface as an abstract base type defining common expected method signatures to allow you to pass around / collect / manage different specialized probe types.

You would embed Probe into each specialized probe type, and methods and fields would be promoted according to rules of embedding. It looks like you're familiar with embedding in general, but the details are worth reviewing in the "Embedding" section of Effective Go if you haven't looked at it recently.

You could override Probe methods in specialized probe types to execute type-specific code.

It might look something like this:

type ProbeInterface interface {
     New()
     Exec() bool
     // whatever other methods are common to all probes
}

type Probe struct {
    // whatever's in a probe
}

func (p *Probe) New() {
    // init stuff
}

func (p *Probe) Exec() bool {
    // exec stuff
    return true
}

// Probe's methods and fields are promoted to ShellProbe according to the rules of embedding
type ShellProbe struct {
    Probe
    // any specialized ShellProbe fields
}

// override Probe's Exec() method to have it do something ShellProbe specific.
func (sp *ShellProbe) Exec() bool {
    // do something ShellProbe-ish
    return true
}

type LoadableProbe struct {
    Name        string
    P           ProbeInterface
    Active      bool
}

func RegisterProbe(name string, probe ProbeInterface, state bool) {
    LoadableProbes = append(LoadableProbes, LoadableProbe{name, probe, state})
}

Upvotes: 1

Jonathan Hall
Jonathan Hall

Reputation: 79516

There are different approaches to your question.

The most direct answer would be: You need to convert your interface{} to a concrete type before calling any methods on it. Example:

probe.Probe.(ShellProbe).New(...)

But this is a really confusing API to use.

A better approach is probably to re-think your entire API. It's hard to do this level of design thinking with the limited information you've provided.

I don't know whether this will work for you, but a common pattern is to define an interface:

type Probe interface {
    New(string, JsonConfig)
    Exec() bool
    // ... etc
}

Then make all of your probe types implement the interface. Then use that interface instead of interface{}, as you initially did:

type LoadableProbe struct {
    Name   string
    Probe  Probe
    Active bool
}

Then your syntax should work again, because the Probe interface includes a New method.

probe.Probe.New(...)

Upvotes: 0

Related Questions