Go:带接口的结构的动态类型转换/断言(调用方法和使用结构公地)



我绞尽脑汁想让我的代码更简洁。问题是在一个函数中,它与不同的structs, implementsinterface一起工作。

在某些情况下,我需要model变量来实现结构(rowModel的切片)([]rowModel),有时我需要使用接口方法。代码不短,很抱歉。所以我在下面的代码中添加了主注释。

接口:

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

这些接口是为方法调用创建的,并使代码更短。但是接口不能像OOP中的抽象类那样有字段或结构。其中一个模型在这里:

 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:

为动态生成结构体修改了一些东西。现在一切都在正确编译,直到我需要得到struct实例的地方。它只看到接口…注释和代码更新在这里:

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
}

根据您在newItem函数中跳过前六个字段的方式,看起来像是这些属性:

type BaseModel struct {
    Date             string
    Hour             int
    Id_user          int
    Id_line          float64
    Id_region        int
    Id_tree_devision int
}

对于所有模型都是通用的。为什么不嵌入这些价值观呢?


是否有一些原因你的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,这将允许您生成正确的StatRow值,而无需切换字符串值。


正如您所注意到的,receiveLightWork中的开关的每种情况本质上都是相同的:您创建新元素的切片,以某种方式"聚集"它们,格式化输出,并返回它。

片的创建可以通过一个类似Factory的接口完成,如上所述。ClusterData已经是一个接口方法。FormatOutput可能应该是。

如果你移动逻辑,这取决于你正在处理的数据类型到这些类型的方法,我认为它应该有可能实现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
    }

也许这是一种愚蠢的方法,因为我不知道更好的方法。
这里是你可以使用的示例代码(这只是草稿)。

首先创建新函数将[]接口{}转换为模型:

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

我得到了很多支持:在Stackoverflow网站上有2个答案,在Slack聊天中也得到了很多gopers的帮助和支持。

正如我在与更熟练的开发人员的夜间谈话中意识到的那样,我试图创建OOP语言中常用的"泛型"(例如最新版本的php)。

我想(并且仍然想)创建一个紧凑的方法,它可以通过其类型名称很好地与任何模型结构一起工作。但是,即使使用reflection,我也没有找到解决方案,当有可能不输入精确的struct type来进行断言时。

. .我的代码变得更短了,但我没有达到主要目标:

"方法的代码不应该依赖于模型结构的数量,这可能会出现。"

那是我自己的目标,我没有给别人安排任务。只有我自己优化。但失败的事实令人悲伤。所以我的,不是最后的,但是最后的版本是这样的:

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方法的代码不依赖于模型的数量),我会很高兴听到它!

相关内容

  • 没有找到相关文章

最新更新