在Go语言中,实现不同但相似类型的数组行为的最佳方法是什么?

huangapple go评论83阅读模式
英文:

Best way to get array-like behavior of different but similar types in Go?

问题

场景:
我有几个不同的客户端,每个客户端与不同的API进行交互。

这些客户端的数据字段是相同的:

type clientX struct {
   key string
   secret string
   client *http.Client
}

然而,这些客户端每个都有许多不同的方法:

func (c *ClientX) someMethod() (*ResponseType, error) {
    //code
}

客户端的数量可能随时间变化,因为可能会添加对新API的支持,或者某些API可能下线。因此,主包中的所有函数都需要是模块化的,并且能够接受可变数量的客户端作为参数。

如何解决这个问题最好呢?
我不能将客户端放入一个数组中,因为它们是不同的类型。

我正在考虑的一些想法:

  1. 首先,我想到的解决方案是使用类型为interface{}的数组。然而,我担心[]interface{}的性能以及在遍历数组时识别客户端类型(类型断言)会导致代码膨胀。

  2. 我对继承的了解不够深入,所以我不确定这是否可行。我设想创建一个包含key、secret和client数据字段的父类Client。具体的客户端将作为继承自父类Client的子类,并定义特定于该客户端的所有方法。根据我基本的理解,我可以通过将所有这些客户端放入一个数组中,并将数组类型定义为Client来实现。这让我有点困惑,因为数组中的元素将不是ClientX类型,而是更一般的Client类型。这是否会导致需要再次进行类型断言,即与解决方案1中相同的问题?如果我无论如何都要进行类型断言,那么将数组类型定义为Client与将数组类型定义为interface{}相比,是否有任何性能上的优势?

  3. 将客户端(clientA、clientB、clientC)作为全局变量。任何函数都可以访问它们,因此我不需要将它们作为参数传递。为了处理可变数量的客户端(数量在运行时决定),我将使用一个clientsEnabled map[string]bool来让函数识别要使用哪些客户端和要忽略哪些客户端。这似乎会导致最小的代码膨胀。然而,我对使用全局变量持谨慎态度,并且一直被教导除非绝对必要,否则应避免使用全局变量。

  4. Stack Overflow 社区提供的另一个解决方案。

希望能得到一些反馈,谢谢。

英文:

Scenario:
I have a few different clients, each interacting with a different API.

The data fields of these clients are the same:

type clientX struct {
   key string
   secret string
   client *http.Client
}

However these clients each have many methods (all different from each other):

func (c *ClientX) someMethod() (*ResponseType, error) {
    //code
}

The amount of clients may change over time, as support for new APIs is added, or some number of APIs go offline. Therefore, all functions in the main package need to be modular and adaptable to accept a variable number of clients as arguments.

What is the best way to go about this problem?
I can't put the clients in an array because they are different types.

Ideas I'm toying around with:

  1. The first solution that comes to mind is an array of type interface{}. However I'm concerned of the performance of an []interface{} as well as the code-bloat I'll have from identifying client types when iterating through the array (type-assertions).

  2. I'm not as educated on inheritance as I'd like to be, so I'm not sure if this works. I'm envisioning creating a parent class Client containing the key, secret, and client data fields. The specific clients will be subclasses inheriting from the parent class Client, and then defining all the methods specific to that client. From my basic understanding, I could then put all of these clients into an array by defining the array type as Client. This leaves me a bit confused on how the elements in the array would behave as the would not be of type ClientX but of the more general type Client. Would this lead to having to type assert all over again aka the same problem as in solution 1? If Im going to have to ID and assert the type anyway are there any performance benefits to having an array of type Client over an array of type interface?

  3. Have the clients (clientA, clientB, clientC) be global variables. Any function can access them so I won't have to pass them as arguments. In order to handle variable clients (the number is decided at runtime) I would have a clientsEnabled map[string]bool for functions to identify which clients to use and which to ignore. This seems like it would have minimal code-bloat. However, I'm wary of using globals, and was always taught to avoid unless absolutely necessary.

  4. Another solution that the SO community has

Would love to have some feedback, thank you.

答案1

得分: 1

根据给定的信息,听起来你应该定义一个接口类型,封装各种客户端类型的共同点。这是实现你在第2点中谈到的方式。请记住,Go语言没有类和子类的概念,所以如果你有一个类型ClientX,然后将其嵌入到其他类型中(Go语言中最接近子类化的方式),那么这些其他类型不能在期望ClientX类型的地方使用。唯一的方法是通过定义接口来表示多个不同底层类型的一个类型。

除了ClientX结构中的共同字段之外,我假设你还需要有某种特定于客户端的处理函数。该函数将执行特定于客户端的操作(如果不了解你正在做什么的更多细节,很难给出更具体的建议)。

这里是一个简单的示例:

package main

import (
	"fmt"
	"net/http"
)

type client interface {
	getKey() string
	getSecret() string
	getHttpClient() *http.Client
	handler()
}

type clientX struct {
	key    string
	secret string
	client *http.Client
}

func (c *clientX) getKey() string {
	return c.key
}

func (c *clientX) getSecret() string {
	return c.secret
}

func (c *clientX) getHttpClient() *http.Client {
	return c.client
}

type clientOne struct {
	clientX
	data1 string
	data2 int
	// ...
}

func (c *clientOne) handler() {
	fmt.Printf("clientOne handler: data1: %q\n", c.data1)
}

func main() {
	c1 := &clientOne{
		clientX: clientX{
			key:    "abc",
			secret: "def",
		},
		data1: "some data",
	}

	var clients []client

	clients = append(clients, c1)

	for _, c := range clients {
		fmt.Printf("key %q secret %q\n", c.getKey(), c.getSecret())
		c.handler()
	}
}

这个示例定义了一个client接口,其中包含了getKeygetSecretgetHttpClienthandler方法。然后,通过嵌入clientX结构体来实现clientOne类型,并在clientOne类型中实现了handler方法。在main函数中,我们创建了一个clientOne实例,并将其添加到clients切片中。然后,我们遍历clients切片,调用接口方法并执行相应的处理函数。

希望这个示例能帮助到你!

英文:

From the information given, it sounds like you should define an interface type that encapsulates what the various client types have in common. That would be the way to actually achieve what you talk about in #2. Remember that Go does not have the concept of classes and subclasses, so if you have a type ClientX and then embed that in other types (the closest thing Go has to subclassing), those other types cannot be used where a type ClientX is expected. The only way to have one type that can represent several different underlying types is by defining an interface.

In addition to the common fields in the ClientX struct, I assume you will have to have some sort of client-specific handler function. That function would then do whatever client-specific operations are necessary. (It's hard to be more specific without knowing more details about what you're doing.)

Here's a simple example:

package main

import (
	"fmt"
	"net/http"
)

type client interface {
	getKey() string
	getSecret() string
	getHttpClient() *http.Client
	handler()
}

type clientX struct {
	key    string
	secret string
	client *http.Client
}

func (c *clientX) getKey() string {
	return c.key
}

func (c *clientX) getSecret() string {
	return c.secret
}

func (c *clientX) getHttpClient() *http.Client {
	return c.client
}

type clientOne struct {
	clientX
	data1 string
	data2 int
	// ...
}

func (c *clientOne) handler() {
	fmt.Printf("clientOne handler: data1: %q\n", c.data1)
}

func main() {
	c1 := &clientOne{
		clientX: clientX{
			key:    "abc",
			secret: "def",
		},
		data1: "some data",
	}

	var clients []client

	clients = append(clients, c1)

	for _, c := range clients {
		fmt.Printf("key %q secret %q\n", c.getKey(), c.getSecret())
		c.handler()
	}
}

答案2

得分: 1

首先,Go语言中没有继承的概念。我强烈建议你阅读一下关于Go语言接口的文章。

对于这个具体的问题,我建议你不要存储_interface{}_实例,而是引入一个_command_接口,并为应用程序中的每个API实现该接口。可以参考这个示例:https://play.golang.org/p/t5Kldpbu-P。

由于你提到客户端命令之间没有共同的行为,除非它们之间存在相互依赖关系,否则我不会为它们的方法引入接口。在这种情况下,接口会使它们更容易进行单元测试(创建一个实现接口的模拟对象)。

这种方法简单且易于扩展。

英文:

First of, there is no inheritance in golang. Also, I'd highly recommend reading about interfaces in golang.

As for this problem specifically, instead of storing interface{} instances, I'd introduce a command interface and implement it for each API with which the application would have to work. Something like this: https://play.golang.org/p/t5Kldpbu-P.

Since you mentioned that there is no common behaviour amongst the client commands, I wouldn't introduce interfaces for their methods unless there are inter-dependencies amongst them. Interfaces in that case would make them easy to unit test (create a mock that implements the interface).

This is simple and easy to extend.

huangapple
  • 本文由 发表于 2016年12月31日 05:47:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/41403068.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定