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:
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).
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?
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.
Another solution that the SO community has
Would love to have some feedback, thank you.
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.
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
", 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
", c.getKey(), c.getSecret())
c.handler()
}
}