Reputation: 774
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
}
Upvotes: 7
Views: 3318
Reputation: 828
Based on how you're skipping over the first six fields in your newItem
function, it seems like these properties:
type BaseModel struct {
Date string
Hour int
Id_user int
Id_line float64
Id_region int
Id_tree_devision int
}
are common to all models. Why not embed these values?
Is there some reason your OrgPack
struct can't just hold a nextIdLine int
value or something along those lines? I think that might result in cleaner code than using reflection and slice lengths to compute row id values.
If you did the above two things, you could easily also replace
func newItem(modelName string, el models.OrgPack) interface{}
with
func (el OrgPack) NewNoagg() Noagg
func (el OrgPack) NewFcr() Fcr
or perhaps
type RowFactory interface { New(el OrgPack) StatRow }
type NoaggFactory struct{}
func (_ NoaggFactory) New(el OrgPack) StatRow
In the latter case, you could attach RowFactory
properties to your OrgPack
s instead of, or in addition to, ModelName
s, which would allow you to produce the correct StatRow
values without needing to switch over string values.
As you've noted, every case of your switch in receiveLightWork
is essentially the same: you create a slice of new elements, "cluster" them somehow, format the output, and return it.
The creation of the slice can be done through a Factory
-like interface, as described above. ClusterData
is already an interface method. FormatOutput
probably should be.
If you move logic that depends on the type of data you're working with into methods for those types, I think it should be possible to achieve a receiveLightWork
that looks like this:
func receiveLightWork(org <-chan models.OrgPack, request ChartOptions) map[string][]OrgStat {
modelClusters := make(map[string][]models.OrgPack)
for orgPack := range org {
if model, ok := modelClusters[orgPack.ModelName]; ok {
modelClusters[orgPack.ModelName] = append(model, orgPack)
} else {
modelClusters[orgPack.ModelName] = []models.OrgPack{orgPack}
}
}
customFilter := request.Filters
customFilter.Cluster = "clusterDay"
output := make(map[string][]OrgStat)
for modelName, slice := range modelClusters {
if len(slice) == 0 {
continue
}
model := slice[0].ModelFactory.New()
for _, el := range slice {
model.Add(el.RowFactory.New(el))
}
model.ClusterData(customFilter)
for sourceName, charts := range request.Charts {
output = model.FormatOutput(output, sourceName, charts)
}
}
return output
}
Upvotes: 3
Reputation: 774
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!
Upvotes: 1
Reputation: 828
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
}
}
Upvotes: 2
Reputation: 2668
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.
Upvotes: 2