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
}
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
}