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.
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 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.
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.
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.
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.
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
}
}
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With