Go:带有接口的结构的动态类型 cast/assertion(调用方法和使用结构公用)
Go: Dynamic type cast/assertion of struct's with interface (to call methods and use struct commons)
我绞尽脑汁试图让我的代码更短更清晰。问题出在一个函数中,它使用不同的 structs
,implements
一个 interface
。
在某些情况下,我需要 model
变量来实现结构(rowModel 的切片)([]rowModel),有时我需要使用接口中的方法。
代码不短,抱歉。所以我把主要注释放在下面的代码中。
这是界面:
type StatModel interface {
FilterData(Filter)
ClusterData(Filter)
CountDataForChart(string)[]ChartElement
GroupByTreeGroups(Filter)[]OrgPack
}
type StatRow interface {
Count( name string) float64
}
此接口是为方法调用而创建的,旨在缩短代码。但是接口不能像 OOP 中的 Abstruct class 那样具有字段或结构。其中一个模型在这里:
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
}
所以,我正在从频道读取数据,获取不同的结构,并尝试正确计算所有内容。在这种情况下如何正确进行类型断言和方法调用?
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
}
非常感谢所有帮助。如有必要,我将回答有关此主题的每个问题。
更新 1:
为动态生成结构修改了一些东西。现在一切都在正确编译,直到我需要获取结构实例的地方。它只看到界面。评论和代码更新在这里:
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
}
也许这是愚蠢的方法,因为我不知道更好的方法。
这里是您可以使用的示例代码(这只是草稿)。
首先创建新函数将 []interface{} 转换为模型 :
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
}
}
您的代码 "dataModel := data_slice.(model)" 替换如下:
dataModel := GetModel(modelName, data_slice)
//now your dataModel is ready to convert to StatModel
if statModel, ok := dataModel.(StatModel); ok {
statModel.FilterData(?) //just example
}
也许这可以给你一些想法。
我相信您已经知道,但是使用这么多 interface{}
实体会使代码难以阅读,并且失去了使用类型安全语言的许多优势。
如果有时间稍微重新设计一些东西,我会质疑接口类型通道确实是必需的这一假设。也许您可以改用多个频道。
然后,当你从你的频道阅读时,你会写:
for !done {
select {
case model := <- noaggModelChan:
doThingsWithNoaggModel(model)
case model := <- fcrModelChan:
doThingsWithFcrModel(model)
case done = <- doneChan:
continue
}
}
我得到了很多支持:此 Whosebug
站点上有 2 个答案,Slack
来自 Gophers 的聊天室提供了很多帮助和支持。
正如我在与更熟练的开发人员的夜间谈话中意识到的那样,我正在尝试创建 OOP 语言中常用的 "generics"(例如 php 的最新版本)。
我想要(并且仍然想要)创建一种紧凑的方法,它可以很好地与任何模型结构的类型名称一起使用。但是即使使用 reflection
我也没有找到解决方案,因为可以不输入准确的 struct type
来断言。
所以..我的代码变短了,但我没有达到主要目标:
"The code of the method should not depend of the amount of model structures,
that can appear."
那是我自己的目标,我不是在给别人做任务。只有我自己的优化。但失败的事实令人难过。所以我的,不是最终的,但最后一个版本是这样的:
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
}
如您所见,此方法的长度直接取决于模型的数量,我无法将此代码放入模型中,因为它是为类型转换而创建的,以便为以后的数学创建正确的结构。
如果有人知道如何实现最后一个目标(receiveLightWork
方法的代码不取决于模型数量),我将很高兴听到!
根据您在 newItem
函数中跳过前六个字段的方式,这些属性似乎是:
type BaseModel struct {
Date string
Hour int
Id_user int
Id_line float64
Id_region int
Id_tree_devision int
}
所有型号通用。为什么不 embed 这些值?
您的 OrgPack
结构是否有某些原因不能仅包含 nextIdLine int
值或类似的东西?我认为这可能会导致比使用反射和切片长度计算行 ID 值更清晰的代码。
如果你做了以上两件事,你也可以很容易地替换
func newItem(modelName string, el models.OrgPack) interface{}
与
func (el OrgPack) NewNoagg() Noagg
func (el OrgPack) NewFcr() Fcr
或者也许
type RowFactory interface { New(el OrgPack) StatRow }
type NoaggFactory struct{}
func (_ NoaggFactory) New(el OrgPack) StatRow
在后一种情况下,您可以将 RowFactory
属性附加到 OrgPack
s 而不是 ModelName
s,或者附加到 ModelName
s,这样您就可以生成正确的StatRow
个值,无需切换字符串值。
正如您所指出的,receiveLightWork
中的每个 switch 案例本质上都是相同的:您创建了一片新元素,"cluster" 它们以某种方式格式化输出,然后 return它。
切片的创建可以通过类似于 Factory
的界面来完成,如上所述。 ClusterData
已经是接口方法了。 FormatOutput
可能应该。
如果将取决于您正在使用的数据类型的逻辑移动到这些类型的方法中,我认为应该可以实现看起来 like this 的 receiveLightWork
:
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
}
我绞尽脑汁试图让我的代码更短更清晰。问题出在一个函数中,它使用不同的 structs
,implements
一个 interface
。
在某些情况下,我需要 model
变量来实现结构(rowModel 的切片)([]rowModel),有时我需要使用接口中的方法。
代码不短,抱歉。所以我把主要注释放在下面的代码中。
这是界面:
type StatModel interface {
FilterData(Filter)
ClusterData(Filter)
CountDataForChart(string)[]ChartElement
GroupByTreeGroups(Filter)[]OrgPack
}
type StatRow interface {
Count( name string) float64
}
此接口是为方法调用而创建的,旨在缩短代码。但是接口不能像 OOP 中的 Abstruct class 那样具有字段或结构。其中一个模型在这里:
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
}
所以,我正在从频道读取数据,获取不同的结构,并尝试正确计算所有内容。在这种情况下如何正确进行类型断言和方法调用?
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
}
非常感谢所有帮助。如有必要,我将回答有关此主题的每个问题。
更新 1:
为动态生成结构修改了一些东西。现在一切都在正确编译,直到我需要获取结构实例的地方。它只看到界面。评论和代码更新在这里:
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
}
也许这是愚蠢的方法,因为我不知道更好的方法。
这里是您可以使用的示例代码(这只是草稿)。
首先创建新函数将 []interface{} 转换为模型 :
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
}
}
您的代码 "dataModel := data_slice.(model)" 替换如下:
dataModel := GetModel(modelName, data_slice)
//now your dataModel is ready to convert to StatModel
if statModel, ok := dataModel.(StatModel); ok {
statModel.FilterData(?) //just example
}
也许这可以给你一些想法。
我相信您已经知道,但是使用这么多 interface{}
实体会使代码难以阅读,并且失去了使用类型安全语言的许多优势。
如果有时间稍微重新设计一些东西,我会质疑接口类型通道确实是必需的这一假设。也许您可以改用多个频道。
然后,当你从你的频道阅读时,你会写:
for !done {
select {
case model := <- noaggModelChan:
doThingsWithNoaggModel(model)
case model := <- fcrModelChan:
doThingsWithFcrModel(model)
case done = <- doneChan:
continue
}
}
我得到了很多支持:此 Whosebug
站点上有 2 个答案,Slack
来自 Gophers 的聊天室提供了很多帮助和支持。
正如我在与更熟练的开发人员的夜间谈话中意识到的那样,我正在尝试创建 OOP 语言中常用的 "generics"(例如 php 的最新版本)。
我想要(并且仍然想要)创建一种紧凑的方法,它可以很好地与任何模型结构的类型名称一起使用。但是即使使用 reflection
我也没有找到解决方案,因为可以不输入准确的 struct type
来断言。
所以..我的代码变短了,但我没有达到主要目标:
"The code of the method should not depend of the amount of model structures, that can appear."
那是我自己的目标,我不是在给别人做任务。只有我自己的优化。但失败的事实令人难过。所以我的,不是最终的,但最后一个版本是这样的:
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
}
如您所见,此方法的长度直接取决于模型的数量,我无法将此代码放入模型中,因为它是为类型转换而创建的,以便为以后的数学创建正确的结构。
如果有人知道如何实现最后一个目标(receiveLightWork
方法的代码不取决于模型数量),我将很高兴听到!
根据您在 newItem
函数中跳过前六个字段的方式,这些属性似乎是:
type BaseModel struct {
Date string
Hour int
Id_user int
Id_line float64
Id_region int
Id_tree_devision int
}
所有型号通用。为什么不 embed 这些值?
您的 OrgPack
结构是否有某些原因不能仅包含 nextIdLine int
值或类似的东西?我认为这可能会导致比使用反射和切片长度计算行 ID 值更清晰的代码。
如果你做了以上两件事,你也可以很容易地替换
func newItem(modelName string, el models.OrgPack) interface{}
与
func (el OrgPack) NewNoagg() Noagg
func (el OrgPack) NewFcr() Fcr
或者也许
type RowFactory interface { New(el OrgPack) StatRow }
type NoaggFactory struct{}
func (_ NoaggFactory) New(el OrgPack) StatRow
在后一种情况下,您可以将 RowFactory
属性附加到 OrgPack
s 而不是 ModelName
s,或者附加到 ModelName
s,这样您就可以生成正确的StatRow
个值,无需切换字符串值。
正如您所指出的,receiveLightWork
中的每个 switch 案例本质上都是相同的:您创建了一片新元素,"cluster" 它们以某种方式格式化输出,然后 return它。
切片的创建可以通过类似于 Factory
的界面来完成,如上所述。 ClusterData
已经是接口方法了。 FormatOutput
可能应该。
如果将取决于您正在使用的数据类型的逻辑移动到这些类型的方法中,我认为应该可以实现看起来 like this 的 receiveLightWork
:
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
}