Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go: Dynamic type cast/assertion of struct's with interface (to call methods and use struct commons)

I cracked my brain trying to make my code shorter and cleaner. The problem is in one function, that is working with different structs, that implements one interface.

In some cases I need the model variable to implement the structure (slice of rowModel's) ([]rowModel) and some times I need to use methods from interface. The code is not short, sorry for that. So I put main comments in the code below.

Here is interface:

type StatModel interface {
    FilterData(Filter)
    ClusterData(Filter)
    CountDataForChart(string)[]ChartElement
    GroupByTreeGroups(Filter)[]OrgPack
}

type StatRow interface {
    Count( name string) float64
}

This interfaces are created for methods calls, and to make code shorter. But Interface cannot have fields or structure as Abstruct class in OOP. One of the models is here:

 type NoaggModel []NoaggRow

 type NoaggRow struct {
    Date             string
    Hour             int
    Id_user          int
    Id_line          float64
    Id_region        int
    Id_tree_devision int
    N_inb            float64
    N_out            float64
    N_hold           float64
    N_abandon        float64
    N_transfer       float64
    T_inb            float64
    T_out           float64
    T_hold           float64
    T_ring           float64
    T_acw            float64
    T_wait           float64
}

type FcrModel  []FcrRow

type FcrRow struct {
    Date             string
    Hour             int
    Id_user          int
    Id_line          float64
    Id_region        int
    Id_tree_devision int
    N_irr            float64
    N_inb            float64
}

So , I'm reading from channel, and getting different structures, and trying to calculate everything correctly. How to make type assertion and method calls correctly in this case?

func receiveLightWork(org <-chan models.OrgPack, request ChartOptions) interface{} {

    modelClusters := make(map[string][]models.OrgPack)

    // here  I fill data into modelClusters

    output := make(map[string][]OrgStat)


    // here I begin loop over clusters of different model types

    for modelName, slice := range modelClusters {

        //here I can't choose what to write
        // model must be convertable to NoaggModel, that is []NoaggRow{}
        // as others AcsiModel, FcrModel ...etc. 
        // Also model.ClusterData(customFilter) must be callable as it is in interface of common model

        var model []interface{} 

        var rowModel interface{}

        switch modelName {

        case "noagg":
            model = model.(models.NoaggModel)
            rowModel = rowModel.(models.NoaggRow{})
        case "acsi":
            model = model.(models.AcsiModel)
            rowModel = rowModel.(models.AcsiRow)
        case "fcr24":
            model = model.(models.FcrModel)
            rowModel = rowModel.(models.FcrRow)
        case "aic":
            model = model.(models.AicModel)
            rowModel = rowModel.(models.AicRow)
        }

        for _, el := range slice {


            modelFields := reflect.ValueOf(&rowModel).Elem()
            sliceFields := reflect.ValueOf(&el.SummorisedData).Elem()

            fieldsTypes := modelFields.Type()

            for i := 6; i < modelFields.NumField(); i++ {
                fmt.Println(" model_field ", fieldsTypes.Field(i).Name )
                modelField := modelFields.Field(i);
                sliceField := sliceFields.Index(i-6) ;

                modelField.Set(reflect.Value(sliceField));
            }

            id_line := sliceFields.Index(len(el.SummorisedData) - 1) ;
            date := sliceFields.FieldByName("PackName");

            modelFields.FieldByName("Id_line").Set(id_line)
            modelFields.FieldByName("Date").Set(date)

     // here append not works, because model is []interface{} and not []NoaggRow or others.
     // Writes [non-interface type []interface {} on left]
            model = append(model, rowModel)
        }


 // here I need to call interface method for model     
        model.ClusterData(customFilter) // now here is unresolved Reference 'ClusterData'

        for _, mod := range model {
          // here some common logick for creating data for chart output
         }    
    }

    return output
}

All help is very highly appreciated. I'll answer to each question on this topic if necessary.

Update 1:

Have modified few things for generating struct's on the fly. Now all is compiling correctly until the place, where I need to get instance of struct. It sees only interface.. The comments and code update is here:

func typeSwitch(model string) (interface{}, interface{}){

    switch model{
        case "noagg":
            fmt.Println("Model type:", model)
            return &models.NoaggModel{}, &models.NoaggRow{}
        case "acsi":
            fmt.Println("Model type:", model)
            return &models.AcsiModel{}, &models.AcsiRow{}
        case "fcr24":
            fmt.Println("Model type:", model)
            return &models.FcrModel{}, &models.FcrRow{}
        case "aic":
            fmt.Println("Model type:", model)
            return &models.AicModel{}, &models.AicRow{}
        default:
            fmt.Println("Unknown")
            return false,false
    }
}


func receiveLightWork(org <-chan models.OrgPack, request ChartOptions) interface{} {

    modelClusters := make(map[string][]models.OrgPack)

    for orgPack := range org {
        // here I fill data into clusters
    }

    output := make(map[string][]OrgStat)

   // here I need common code to put data from clusters in correct structures and call interface methods

    for modelName, slice := range modelClusters {

        model, rowModel := typeSwitch(modelName)

        var data_slice []interface{}

        for _, el := range slice {

            modelFields := reflect.ValueOf(rowModel).Elem()
            fieldsCounter := modelFields.NumField()

            sliceFields := reflect.ValueOf(&el.SummorisedData).Elem()
            sliceObjFields := reflect.ValueOf(&el).Elem()

            fieldsTypes := modelFields.Type()

            for i := 6; i < fieldsCounter; i++ {
                fmt.Println(" model_field ", fieldsTypes.Field(i).Name )
                modelField := modelFields.Field(i);
                sliceField := sliceFields.Index(i-6) ;

                modelField.Set(reflect.Value(sliceField));
            }

            id_line := sliceFields.Index(len(el.SummorisedData) - 1) ;
            date := sliceObjFields.FieldByName("PackName");


            modelFields.FieldByName("Id_line").Set(id_line)
            modelFields.FieldByName("Date").Set(date)

            fmt.Println("row_data : ", rowModel)
            data_slice = append(data_slice, rowModel)
        }

    // here comes : invalid type assertion: data_slice.(model) (non-interface type []interface {} on left           
        dataModel := data_slice.(model)
    // here I need correctly created instance of model 
    // (NoaggModel or FcrModel) with data inside its struct 
    // to work with it and call interface methods that are shown in interface above

    }

    return output
}
like image 989
Altenrion Avatar asked Aug 19 '16 16:08

Altenrion


People also ask

Can a struct have an interface Golang?

Like a struct an interface is created using the type keyword, followed by a name and the keyword interface . But instead of defining fields, we define a “method set”. A method set is a list of methods that a type must have in order to “implement” the interface.

What is the difference between struct and interface in Golang?

Structs and interfaces are Go's way of organizing methods and data handling. Where structs define the fields of an object, like a Person's first and last name. The interfaces define the methods; e.g. formatting and returning a Person's full name.

What is type assertion in Golang?

Golang provides a simple way of checking if an interface value is of a specific type. This is called type assertion, and it is generally used to make sure that an interface value satisfies another interface or to find the concrete type of the interface.

How do you extend a structure in Golang?

We cannot directly extend structs but rather use a concept called composition where the struct is used to form other objects. So, you can say there is No Inheritance Concept in Golang.


3 Answers

I'm sure you're already aware, but using this many interface{} entities makes the code kind of hard to read and loses a lot of the advantages of working with a type-safe language.

If there's time to slightly re-design things, I'd question the assumption that a channel of interface types is really necessary. Maybe you could use multiple channels instead.

Then, when you would read from your channel(s), you would write:

for !done {
  select {
    case model := <- noaggModelChan:
      doThingsWithNoaggModel(model)
    case model := <- fcrModelChan:
      doThingsWithFcrModel(model)
    case done = <- doneChan:
      continue
  }
}
like image 200
Jesse Amano Avatar answered Oct 23 '22 07:10

Jesse Amano


Maybe this is the stupid way, because I don't know the better way to do it.
Here the sample code you can use (this only draft).

First make new function to convert []interface{} to model :

func GetModel(modelName string, data []interface{}) interface{} {
    switch modelName {
        case "noagg" :
            m := make(NoaggModel, len(data))
            for i, v := range data {
                m[i] = v.(NoaggRow)
            }
            return m
        case .....
        //and case so on
    }
}

And your code "dataModel := data_slice.(model)" replace like below :

dataModel := GetModel(modelName, data_slice)
//now your dataModel is ready to convert to StatModel
if statModel, ok := dataModel.(StatModel); ok {
    statModel.FilterData(?) //just example
}

Maybe this can give you some idea.

like image 26
Apin Avatar answered Oct 23 '22 07:10

Apin


I received a lot of support: 2 answers on this Stackoverflow site and many help and support on Slack chat from Gophers.

As I realized from my night talks with more skilled developers, I was trying to create "generics" that are commonly used in OOP languages(as example latest versions of php).

I wanted (and still want) to create one compact method, that would work nicely with any model structure by name of its type. But even with the use of reflection I didn't find a solution, when it is possible not to type exact struct type for making assertion to.

So.. My code became shorter, but I didn't reached the main goal:

"The code of the method should not depend of the amount of model structures, that can appear."

That is my own goals, I'm not making somebodies tasks. Only my own optimization. But the fact of the fail is sad. So my, not final, but last version is this :

func newItem(modelName string, el models.OrgPack) interface{} {

    var item models.StatRow

    switch modelName {
    case "noagg":
        item = &models.NoaggRow{}
    case "fcr24":
        item = &models.FcrRow{}
    case "acsi":
        item = &models.AcsiRow{}
    case "aic":
        item = &models.AicRow{}
    case "aux":
        item = &models.AuxRow{}
    case "cti":
        item = &models.CtiRow{}
    case "srv":
        item = &models.SrvRow{}
    case "sale":
        item = &models.SaleRow{}
    case "pds":
        item = &models.PdsRow{}
    case "wfm":
        item = &models.WfmRow{}
    }

    modelFields := reflect.ValueOf(item).Elem()

    fieldsCounter := modelFields.NumField()

    sliceFields := reflect.ValueOf(&el.SummorisedData).Elem()
    sliceObjFields := reflect.ValueOf(&el).Elem()

    fieldsTypes := modelFields.Type()

    for i := 6; i < fieldsCounter; i++ {
        fmt.Println(" model_field ", fieldsTypes.Field(i).Name)
        modelField := modelFields.Field(i);
        sliceField := sliceFields.Index(i - 6);

        modelField.Set(reflect.Value(sliceField));
    }

    id_line := sliceFields.Index(len(el.SummorisedData) - 1);
    date := sliceObjFields.FieldByName("PackName");

    modelFields.FieldByName("Id_line").Set(id_line)
    modelFields.FieldByName("Date").Set(date)

    return item

}

func formatOutput(output map[string][]OrgStat, sourceName string, modelName string, charts []Chart, mod models.StatRow, cluster string) map[string][]OrgStat {

    if sourceName == modelName {
        var stats []OrgStat
        for _, chart := range charts {
            stats = append(stats, OrgStat{Name:chart.Name, Value: mod.Count(chart.Name)})
        }
        _, group_exist := output[cluster]
        if group_exist {
            inserted_stat := output[cluster]
            output[cluster] = append(stats, inserted_stat...)
        }else {
            output[cluster] = stats
        }

    }
    return output
}

func receiveLightWork(org <-chan models.OrgPack, request ChartOptions) interface{} {

    modelClusters := make(map[string][]models.OrgPack)

    for orgPack := range org {
        _, ok := modelClusters[orgPack.ModelName]
        if ok {
            model := modelClusters[orgPack.ModelName]
            model = append(model, orgPack)
            modelClusters[orgPack.ModelName] = model

        }else {
            var modelSlice []models.OrgPack
            modelSlice = append(modelSlice, orgPack)
            modelClusters[orgPack.ModelName] = modelSlice
        }
    }

    output := make(map[string][]OrgStat)

    for modelName, slice := range modelClusters {

        switch modelName {
        case "noagg":
            model := models.NoaggModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.NoaggRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)


            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "acsi":
            model := models.AcsiModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.AcsiRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "fcr24":
            model := models.FcrModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.FcrRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "aic":
            model := models.AicModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.AicRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "aux":
            model := models.AuxModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.AuxRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "cti":
            model := models.CtiModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.CtiRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "srv":
            model := models.SrvModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.SrvRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "sale":
            model := models.SaleModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.SaleRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "pds":
            model := models.PdsModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.PdsRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        case "wfm":
            model := models.WfmModel{}

            for _, el := range slice {
                newElement := newItem(modelName, el)
                model = append(model, *(newElement.(*models.WfmRow)))
            }
            customFilter := request.Filters
            customFilter.Cluster = "clusterDay"

            model.ClusterData(customFilter)

            for _, mod := range model {
                for sourceName, charts := range request.Charts {
                    output = formatOutput(output, sourceName, modelName, charts, mod, mod.Date)
                }
            }
        }
    }

    return output
}

As you can see, the length of this methods depends directly on the amount of models, and I cannot land this code into the models, because it is made for type casting to create correct structures for later math.

If anyone have any idea, how to reach this last goal(code of receiveLightWork method not depend on models amount), I'll be happy to hear it!

like image 1
Altenrion Avatar answered Oct 23 '22 07:10

Altenrion