英文:
How to make an object of a class and have getters for struct?
问题
我最近开始使用golang
,在理解如何实现在Java
或C#
中轻松完成的相同任务时遇到了困难。我正在尝试创建一个configmanager
类的对象,并且当第一次调用configmanager
类时,它应该初始化所有的配置并将其存储在内存中的某个结构对象中。然后,我应该能够访问configmanager
对象,并且可以使用一些getter从我的主函数中访问所有这些配置。
下面是我的configmanager
go类。为了更容易理解,它现在非常简单。
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
对象将其加载到内存中。如上所示,一切都正常工作。
问题陈述
由于我来自Java
和C#
背景,所以我困惑的是如何创建configmanager
类的对象,以及在第一次调用configmanager
时如何初始化所有的配置,并且如何通过一些getter访问ClientMetrics
结构对象。在Java和C#中,我习惯于在构造函数中初始化所有这些内容,然后使用一些getter来访问配置。在golang中,我应该如何做同样的事情。
我最困惑的是go中是否有构造函数,以及如何创建getter来在我的主函数中访问结构对象?我只是在寻找更好的设计方式,以及如何在golang中完成上述代码的方式。
更新
我想我没有能够很好地解释清楚。我有X(假设为5)个不同的json文件在一个文件夹中,每个json文件都需要有自己的结构,因为它们是完全不同的json文件。我的configmanager
文件将负责将所有这5个json文件加载到它们自己的结构中,然后我应该能够从configmanager
对象中访问所有这5个struct
及其字段。所有这些都应该在第一次调用configmanager
类时进行初始化。
这只是一个示例,其中我在它们各自对应的文件夹(folderX)
中有一堆json文件。我有三类文件(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
类获取相应的结构及其字段。
例如:读取clientMap-*.json
文件。
files, err := utilities.FindFiles(root, "clientMap-*.json")
// 在反序列化后,将所有clientMap-*.json文件加载到它们自己的结构中
同样适用于dataMap-*.json
文件
files, err := utilities.FindFiles(root, "dataMap-*.json")
// 在反序列化后,将所有dataMap-*.json文件加载到它们自己的结构中
还有process-data.json
文件
files, err := utilities.FindFiles(root, "process-data.json")
// 在反序列化后,将所有process-data.json文件加载到它们自己的结构中
我的FindFiles
方法将找到所有与上述模式匹配的文件,files
变量是一个包含匹配特定模式的所有文件列表的数组。现在我可以创建一个包含所有其他structs
的ConfigManager
结构类型,但我正在寻找一种易于扩展的解决方案,以便将来如果我添加任何其他json文件类别,它应该能够轻松扩展。解决这个问题的正确方法是什么?
英文:
I have recently started working with golang
so having difficulties understanding on how to achieve same thing which I am able to do it easily in Java
or C#
. I am trying to make an object of configmanager
class and when the first time configmanager
class is called, it should initialize all my configs and store it in memory in some struct object. And then I should have access to configmanager
object and should be able to access all those configs from my main function using some getters maybe?
Below is my configmanager
go class. It is very simple for now to make it easier to understand.
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
}
And here is I use it in my main function - This is just basic code just to demonstrate what I am doing but this isn't production
ready code.
package main
import (
"github.com/david/internal/config"
)
func main() {
root := "/home/Configurations"
config.Init(root)
//
}
In my above Init
function, I find process-data.json
file if it is there on the disk and then load it in memory by deserializing it into ClientMetrics
object. Everything works fine as shown above.
Problem Statement
Since I am coming from Java
and C#
background, so 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. In Java and C#, I used to have constructor where I initialize all these things and then have some getters to access the config. How should I do the same thing in golang.
My main confusion is do we have constuctors in go and how should I make getters to access struct object in my main function? I am just looking for better design in go and how should I do my above code in golang?
Update
I think I wasn't able to explain properly. I have X (let's suppose 5 for now) different json files in a folder and each of those json files needs to have their own struct because they are totally different json files. My configmanager
file will be responsible to load all those 5 json files into their own struct and then I should be able to access all those 5 structs
and their fields from the object of configmanager
. All this should happen during the initialization of configmanager
class when it is called for the first time.
Here is just an example where I have bunch of json files in their own corresponding folder (folderX)
. I have three categories of files (clientMap-*.json, dataMap-*.json, process-data.json)
as shown below.
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
Now I need to read all these files (clientMap-*.json, dataMap-*.json, process-data.json)
in their own struct. And I should able to use configmanager
class to get corresponding struct and their fields too after unmarshalling.
For example: Read clientMap-*.json
files.
files, err := utilities.FindFiles(root, "clientMap-*.json")
// load all clientMap-*.json files in its own struct after unmarshalling
Similarly for dataMap-*.json
files
files, err := utilities.FindFiles(root, "dataMap-*.json")
// load all dataMap-*.json files in its own struct after unmarshalling
Also for process-data.json
files
files, err := utilities.FindFiles(root, "process-data.json")
// load all process-data.json files in its own struct after unmarshalling
My FindFiles
method will find all the files even with regex
like above. files
variable is an array containing list of all files matching particular pattern. Now I can create ConfigManager
struct type holding all other structs
for my config but I am trying to find a solution which is easily extensible so that in future if I add any other json file category it should be able to extend easily. What is the correct way to solve this?
答案1
得分: 1
这看起来像是动态的JSON结构解析,该方法是由John Asmuth在2015年的《解码混合结构》中提出的。
你可以在这里运行以下示例。
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)
}
然后,根据读取的原始JSON,你的ConfigManager将实例化正确的Config结构。
英文:
> 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
That looks like dynamic JSON struct unmarshalling, which was presented in 2015 by John Asmuth in decoding with mixed structures
You can run the 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)
}
From there, your ConfigManager will instantiate the right Config structure, depending on the raw JSON read.
答案2
得分: 1
我认为问题在于你从Java/C#的角度来看Go语言,因此很难理解其特性。如果你有时间,我建议你在开始编码之前先学习一些Go语言的教程或书籍(这本书很不错:https://www.amazon.com/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440)。
直接回答你的问题,你需要创建一个返回指向该结构体对象的指针的函数(可以参考这里的一个简单示例:https://gobyexample.com/structs)。
以ClientMetric为例:
func NewClientMetric(ClientID int, CustomerData CustomerData, LegCustomer []int, OtherIds []int, CatID int) (*ClientMetric, error) {
//验证输入参数
//如果有错误,创建一个错误消息并返回 nil, err
//其他通常放在构造函数中的代码放在这里
return &ClientMetric{ClientID, CustomerData, LegCustomer, OtherIds, CatID}, nil
}
在这个例子中,函数NewClientMetric
是构造函数,它返回一个指向新创建对象的指针/引用。它还返回一个错误对象,这相当于构造函数抛出异常。就像在Java中需要使用try/catch一样,在Go语言中你需要检查并处理该函数返回的err变量。
你需要为每个类型创建类似的函数。
至于getter和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())
请注意,以大写字母开头的属性、函数和方法是公共的,其他的是私有的。如果FirstName
写成firstName
,则在声明它的包之外将无法访问。
请注意,我没有检查指针是否为null/nil的错误,但这是一种推荐的做法。
英文:
I think the trouble is that you're looking at Go from a Java/C# perspective and thus struggling to make sense of the features. If you have the time, then I would suggest that you go thru some Go tutorials or books before you start coding (this one is pretty good: https://www.amazon.com/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440)
To answer your question directly, what you need to do is to create a function which returns a pointer to an object of the struct (see here for a simple example: https://gobyexample.com/structs)
Taking ClientMetric as an example:
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
}
In this case, the function NewClientMetric
is the constructor and it returns a pointer/reference to the newly created object. It also returns an error object, which is the same as saying that the constructor throws exceptions. Just as you would need to use try/catch in Java, you would need to check to handle the err variable returned by this function.
You would need to make similar functions for each of your types.
As for getters & setters, generally speaking, that should be avoided in Go. You can access the attributes of a struct directly. A function (like a getter) is only useful if you're going to do something to the attribute before returning it. Something like this:
type Customer struct {
FirstName string
LastName string
}
func (this *Customer) GetFullName() string {
return this.FirstName + " " + this.LastName
}
and these can then be accessed like this:
var customer *Customer
customer = &Customer{"John","Smith")
fmt.Println(customer.FirstName)
fmt.Println(customer.LastName)
fmt.Println(customer.GetFullName())
Please note that attributes, functions and methods which begin with a capital letter are public, the others are private. If FirstName
was written as firstName
, it would not be accessible outside the package in which it was declared.
Please do note that I'm not checking for errors if the pointer is null/nil, but that is always recommended.
答案3
得分: 1
这个问题很难回答,因为你似乎忘记以明确的方式提问,所以我将根据你的描述提取一个问题来开始回答。我认为我们可以从以下内容中得出问题:
> 问题描述
>
> [...] 我困惑的是如何创建一个configmanager
类的对象,以及在第一次调用configmanager
时如何初始化所有的配置,并且通过一些getter方法访问ClientMetrics
结构体。
我认为这里真正的问题是:“如何将读取和解析文件的任务与存储结果供程序使用的任务分离?”。
通常,我们可以通过将事物分解为多个函数/方法来分离关注点,你已经在某种程度上做到了这一点。然而,存储严格来说是一种类型的问题,所以我们需要一个能够保存结果的类型。我将利用这个机会从类型的名称中省略Manager
一词,因为它只是提供了无用的抽象。这个类型并不管理配置。它就是配置,因为它包含了所有的配置。
type Config struct {
ClientMapConfigs []ClientMapConfig
DataMapConfigs []DataMapConfig
ProcessDataConfigs []ProcessDataConfig
}
注意字段以大写字母开头,表示它们是公开的。这意味着其中可能包含无效的数据,因为没有对写入进行保护,这与我们从文件中读取这些数据的事实相一致。在使用这些数据之前,正确的程序必须对其进行验证。然后,你可以通过变量名来表示验证后的数据的有效性。
func main() {
validConfig := getValidConfig("path/to/dir")
// ...
}
func getValidConfig(configDirectoryPath string) *Config {
config, err := NewConfigFromConfigDirectory(configDirectoryPath)
if err != nil {
log.Printf("从目录'%s'读取配置失败:%v\n", configDirectoryPath, err)
os.Exit(1)
}
if err = ValidateConfig(config); err != nil {
log.Printf("从目录'%s'读取的配置未通过验证:%v\n", configDirectoryPath, err)
os.Exit(1)
}
}
func NewConfigFromConfigDirectory(configDirectoryPath string) *Config {
// <- 在这里读取各个配置并添加到切片中
return &Config{ // 这是Go语言中最接近“构造函数”的方式。
ClientMapConfigs: clientMapConfigs,
DataMapConfigs: dataMapConfigs,
ProcessDataConfigs: processDataConfigs,
}
}
注意,验证和读取配置的函数没有立即需要一个接收器(即结构体的方法)。它们作为独立的函数就可以了,直到你的需求以某种方式发生变化,需要引入状态性来处理逻辑。
此外,我在这里使用退出码1
表示错误情况,因为当程序由于恐慌而终止时,Golang使用代码2
。前者可以被视为环境问题,而后者表示程序本身的问题。这是一个有用的区分,并与你可能从Java中了解到的Exception
与RuntimeException
的语义相一致。
英文:
This question is hard to answer because you kind of forgot to ask one in a definitive way, so I will start my answer by extracting a question based on what you wrote. I believe we can do so from this:
> 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
I believe the real question here is "How do I separate the concern of reading and unmarshalling the files from the concern of storing the results for my program to use?".
It's common to separate concerns by breaking things up into multiple functions/methods, which you have already done to some extent. Storage however is strictly a matter of types, so we need a type capable of holding the results. I'll use this opportunity to omit the word Manager
from the type's name, because all it does is to provide useless abstraction. This type does not manage the config. It is the config, in that it contains all of the config.
type Config struct {
ClientMapConfigs []ClientMapConfig
DataMapConfigs []DataMapConfig
ProcessDataConfigs []ProcessDataConfig
}
Notice the fields start with an upper-case letter, making them public. This communicates that there could be nonsense in there, as nothing is protected from writes, which aligns with the fact that we read this data from files. A correct program must validate this data before using it. You could then communicate the validity of the validated data in the variable name.
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,
}
}
Notice that there is no imminent need for the functions validating and reading the config to have a receiver, i.e. be a method of a struct. They're fine as standalone functions until your requirements change in ways which demand the introduction of statefulness for either logic.
Also I use exit code 1
for the error-cases here, because Golang uses code 2
when the program terminates due to a panic. The former can be thought of as a problem with the environment, while the later indicates a problem within the program itself. It's a useful distinction to make, and aligns with the semantics of Exception
versus RuntimeException
which you may know from Java.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论