如何制作 class 的对象并为结构提供吸气剂?

How to make an object of a class and have getters for struct?

我最近开始与 golang 合作,因此很难理解如何实现我在 JavaC# 中可以轻松完成的相同事情。我正在尝试创建 configmanager class 的对象,当第一次调用 configmanager class 时,它应该初始化我的所有配置并将其存储在某个结构的内存中目的。然后我应该可以访问 configmanager 对象,并且应该能够使用一些 getter 从我的主要功能访问所有这些配置?

下面是我的configmanager去class。暂时很简单,让大家更容易理解。

package config

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "github.com/david/internal/utilities"
)

type ClientMetrics struct {
    ClientMetrics []ClientMetric `json:"clientMetrics"`
}
type CustomerData struct {
    Process []string `json:"Process"`
    Mat     []string `json:"Mat"`
}
type ClientMetric struct {
    ClientID     int          `json:"clientId"`
    CustomerData CustomerData `json:"customerData,omitempty"`
    LegCustomer  []int        `json:"legCustomer"`
    OtherIds     []int        `json:"otherIds,omitempty"`
    CatID        int          `json:"catId,omitempty"`
}

func Init(root string) (err error) {
    files, err := utilities.FindFiles(root, "process-data.json")
    if err != nil {
        return fmt.Errorf("cannot find process-data.json file: %v", err)
    }
    for _, file := range files {
        body, err := ioutil.ReadFile(file)
        if err != nil {
            return fmt.Errorf("unable to read file: %v", err)
        }
        var auto ClientMetrics
        json.Unmarshal(body, &auto)

    }

    return nil
}

这里是我在我的主要功能中使用它 - 这只是基本代码,只是为了演示我在做什么,但这不是 production 就绪代码。

package main

import (
    "github.com/david/internal/config"
)

func main() {
    root := "/home/Configurations"
    config.Init(root)

  //
}

在我上面的 Init 函数中,我找到 process-data.json 文件(如果它在磁盘上),然后通过将其反序列化为 ClientMetrics 对象将其加载到内存中。一切正常,如上所示。

问题陈述

由于我来自 JavaC# 背景,所以我的困惑是如何创建 configmanager class 的对象以及我应该如何初始化所有我第一次调用 configmanager 时的配置,还可以使用一些 getter 访问 ClientMetrics 结构。在 Java 和 C# 中,我曾经使用构造函数来初始化所有这些东西,然后使用一些 getter 来访问配置。我应该如何在 golang 中做同样的事情。

我的主要困惑是我们在 go 中有构造函数吗?我应该如何让 getter 访问我的主要函数中的结构对象?我只是在寻找更好的 go 设计,我应该如何在 golang 中完成上面的代码?

更新

我想我无法正确解释。我在一个文件夹中有 X(假设现在有 5 个)不同的 json 文件,每个 json 文件都需要有自己的结构,因为它们是完全不同的 json 文件。我的 configmanager 文件将负责将所有这 5 个 json 文件加载到它们自己的结构中,然后我应该能够从 [= 的对象访问所有这 5 个 structs 及其字段20=]。这一切都应该发生在 configmanager class 首次调用时的初始化过程中。

这只是一个示例,其中我有一堆 json 文件在它们自己的相应文件夹 (folderX) 中。我有三类文件 (clientMap-*.json, dataMap-*.json, process-data.json),如下所示。

Products
├── folder1
│   ├── clientMap-123.json
│   ├── clientMap-987.json
│   ├── clientMap-7161.json
├── folder2
│   ├── dataMap-18271.json
│   ├── dataMap-12921.json
│   ├── dataMap-98121.json
└── folder3
    ├── process-data.json

现在我需要在它们自己的结构中读取所有这些文件 (clientMap-*.json, dataMap-*.json, process-data.json)。我应该能够在解组后使用 configmanager class 来获取相应的结构及其字段。

例如:读取clientMap-*.json个文件。

files, err := utilities.FindFiles(root, "clientMap-*.json")
// load all clientMap-*.json files in its own struct after unmarshalling

dataMap-*.json 个文件类似

files, err := utilities.FindFiles(root, "dataMap-*.json")
// load all dataMap-*.json files in its own struct after unmarshalling

也适用于 process-data.json 个文件

files, err := utilities.FindFiles(root, "process-data.json")
// load all process-data.json files in its own struct after unmarshalling

我的 FindFiles 方法会找到所有文件,即使 regex 像上面一样。 files 变量是一个数组,其中包含与特定模式匹配的所有文件的列表。现在我可以为我的配置创建 ConfigManager 包含所有其他 structs 的结构类型,但我正在尝试找到一个易于扩展的解决方案,以便将来如果我添加任何其他 json 文件类别它应该能够轻松扩展。解决这个问题的正确方法是什么?

I can have let's say 10 different configs (files) and each of those configs can have their own structs since it's a different configs so I need to have separate struct for them

这看起来像是动态 JSON 结构解组,由 John Asmuth in decoding with mixed structures

在 2015 年提出

您可以运行 following example here

type Dog struct {
  Name     string
  Frisbees int
}
type Cat struct {
  Name    string
  Strings int
}
type RawAnimal struct {
  Type string
  Cat
  Dog
}
type Animal RawAnimal
func (a *Animal) UnmarshalJSON(data []byte) error {
  if err := json.Unmarshal(data, (*RawAnimal)(a)); err != nil {
    return err
  }
  switch a.Type {
  case "cat":
    return json.Unmarshal(data, &a.Cat)
  case "dog":
    return json.Unmarshal(data, &a.Dog)
  }
  return fmt.Errorf("unknown type %q", a.Type)
}

从那里,您的 ConfigManager 将实例化正确的配置结构,具体取决于原始 JSON 读取。

我认为问题在于您是从 Java/C# 的角度看待 Go,因此很难理解这些特性。如果你有时间,那么我建议你在开始编码之前阅读一些 Go 教程或书籍(这本非常好:https://www.amazon.com/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440

要直接回答您的问题,您需要做的是创建一个函数,其中 returns 指向结构对象的指针(请参阅此处的简单示例:https://gobyexample.com/structs

以ClientMetric为例:

func NewClientMetric(ClientID int, CustomerData CustomerData, LegCustomer []int, OtherIds []int, CatID int) (*ClientMetric, error) {

//validate your inputs
//in case of errors, create and error message in the variable err and then: return nil, err
//other code which would go into a typical constructor would go here

return &ClientMetric{ClientID,CustomerData, LegCustomer, OtherIds, CatID}, nil
}

在这种情况下,函数 NewClientMetric 构造函数 并且它 returns 是 pointer/reference 新创建的对象。它还 returns 一个错误对象,这与说构造函数抛出异常是一样的。正如您需要在 Java 中使用 try/catch 一样,您需要检查以处理此函数返回的 err 变量。

您需要为每种类型创建类似的函数。

至于getters & setter,一般来说在Go中应该避免。您可以直接访问结构的属性。函数(如 getter)仅在您要在返回属性之前对属性执行某些操作时才有用。像这样:

type Customer struct {
FirstName string
LastName string
}

func (this *Customer) GetFullName() string {
return this.FirstName + " " + this.LastName
}

然后可以像这样访问这些:

var customer *Customer
customer = &Customer{"John","Smith")
fmt.Println(customer.FirstName)
fmt.Println(customer.LastName)
fmt.Println(customer.GetFullName())

请注意,以大写字母开头的属性、函数和方法是public,其他是私有的。如果 FirstName 被写成 firstName,它在声明它的包之外将无法访问。

请注意,如果指针为 null/nil,我不会检查错误,但始终建议这样做。

这个问题很难回答,因为你有点忘记以明确的方式提问,所以我会根据你写的内容提取一个问题来开始我的回答。我相信我们可以这样做:

Problem Statement

[...] my confusion is how can I make an object of configmanager class and how should I initialize all my configs during the first time when I call configmanager and also have access to ClientMetrics struct using some getters

我认为这里真正的问题是“我如何将读取和解组文件的关注与存储结果供我的程序使用的关注分开?”。

通过将事物分解为多个 functions/methods 来分离关注点是很常见的,您已经在某种程度上做到了这一点。然而,存储严格来说是类型的问题,因此我们需要一种能够保存结果的类型。我将借此机会从类型名称中省略 Manager 一词,因为它所做的只是提供无用的抽象。此类型不管理配置。它配置,因为它包含所有配置。

type Config struct {
    ClientMapConfigs   []ClientMapConfig
    DataMapConfigs     []DataMapConfig
    ProcessDataConfigs []ProcessDataConfig
}

请注意字段以大写字母开头,使它们成为 public。这表明那里可能有废话,因为没有任何内容不受写入保护,这与我们从文件中读取此数据的事实一致。正确的程序必须在使用之前验证此数据。然后,您可以在变量名称中传达已验证数据的有效性。

func main() {
    validConfig := getValidConfig("path/to/dir")
    // ...
}

func getValidConfig(configDirectoryPath string) *Config {
    config, err := NewConfigFromConfigDirectory(configDirectoryPath)
    if err != nil {
        log.Printf("Failed to read config from dir '%s': %v\n", configDirectoryPath, err)
        os.Exit(1)
    }
    if err = ValidateConfig(config); err != nil {
        log.Printf("Config from dir '%s' failed to validate: %v\n", configDirectoryPath, err)
        os.Exit(1)
    }
}

func NewConfigFromConfigDirectory(configDirectoryPath string) *Config {
    // <- read individual configs and add to slices here

    return &Config{ // This is the closest to a "constructor" that Go has.
        ClientMapConfigs: clientMapConfigs,
        DataMapConfigs: dataMapConfigs,
        ProcessDataConfigs: processDataConfigs,
    }
}

请注意,验证和读取配置的函数并不迫切需要接收者,即成为结构的方法。它们作为独立函数很好,直到您的需求发生变化,需要为任一逻辑引入有状态。

此外,我对此处的错误情况使用退出代码 1,因为当程序因恐慌而终止时,Golang 使用代码 2。前者可以认为是环境的问题,而后者则是程序本身的问题。这是一个有用的区别,并且符合 ExceptionRuntimeException 的语义,您可能从 Java.

中了解到