refactor methods which use reflection - go

Suppose I have two struct which are
type A struct {
}
func (a *A) SomeFunc (s string) {
// some codes
}
func (a *A) ProcessRequest(funcName string, parameters map[string]*structpb.Value) error {
...
if method := reflect.ValueOf(a).MethodByName(funcName); method.IsValid()
...
values := make([]reflect.Value, 0)
...
rv := method.Call(values)
...
}
type B struct {
}
func (b *B) AnotherFunc (i int) {
//somecode
}
and I am using reflection to call methods in A which is SomeFunc and it is working well.
Now I need to write a struct B which also has ProcessRequest which has exacly same code with the one in A.
Is there anyway I can refactor this?
I have tried to create
type ProcessProvider struct{
}
and let A and B embed it but when the method is called, *ProcessProvider is passed as the receiver and it cannot find SomeFunc and AnotherFunc.
An interface
type ProcessProvider interface {
ProcessRequest(serviceName string, parameters map[string]*structpb.Value) error
}
is helpful but I still have to copy paste the code.

Related

Chainable struct methods that satisfies multiple interfaces?

I have a need to create multiple structs with almost the same fields and methods with the same actions. Instead of doing that I thought, why not create a single struct and use interfaces to limit interactions. It worked great! Now the problem. I want the methods to be chainable. To do that, methods need to return a reference to the struct. After doing that, all the returns complain that (struct) does not implement <interface> (wrong type for <method> method). Which is expected.
The question is, is it possible to use a single struct with multiple interfaces that has chainable methods? Or is creating individual, duplicate, structs for every interface the only way?
I thought of using composition but I still need to define methods that will call the embedded struct methods, in which case there's no difference to creating new pure structs.
Example problem:
https://play.golang.org/p/JrsHATdi2dr
package main
import (
"fmt"
)
type A interface {
SetA(string) A
Done()
}
type B interface {
SetB(string) B
Done()
}
type t struct {
a string
b string
}
func (t *t) SetA(a string) *t { t.a = a; return t }
func (t *t) SetB(b string) *t { t.b = b; return t }
func (t *t) Done() { fmt.Println(t.a, t.b) }
func NewTA() A {
return &t{}
}
func NewTB() B {
return &t{}
}
func main() {
fmt.Println("Hello, playground")
ta := NewTA()
ta.SetA("a")
ta.Done()
tb := NewTB()
tb.SetB("b")
tb.Done()
}
When you use *t as return type in SetA and SetB that means t are not implement A and B interface. The signature of SetA and SetB function of *t doesn't match with interface A and B accordingly.
Accually SetA(a string) A and SetA(a string) *t are not same think. You used A as return type for SetA in interface but use *t as return type for t, go doesn't support this. Same for SetB function
If you do like this then it will work because now function signature matched
func (t *t) SetA(a string) A { t.a = a; return A(t) }
func (t *t) SetB(b string) B { t.b = b; return B(t) }
Code in go playground here

assinging functions to function types that return interface values,

Im trying to define a couple of interface to refactor some code and I'm getting a problem where Go is not letting me assign functions to variables.
This is the set up
main.go
type Gettable interface {
Get() int64
}
type MyFunction func(int64) (Gettable, error)
func main() {
var f MyFunction
f = sub.TestFn2
a, _ := f(1)
fmt.Println(a)
}
main/sub
package sub
type MyStruct struct {
Val int64
}
func (v MyStruct) Get() int64 {
return v.Val
}
func TestFn2(a int64) (MyStruct, error) {
return MyStruct{a}, nil
}
I'm trying to define a generic function type, and in the sub package create the concrete functions
and ideally i want to store the functions in a map and call them with something like
FnMap["fnName"]()
I'm not there yet,
im getting an error saying
/topics.go:27:4: cannot use sub.TestFn2 (type func(int64) (sub.MyStruct, error)) as type MyFunction in assignment
but MyStruct clearly implements the interface Gettable
This error occurs because of signature don't match.
You code shared is:
//shared code
//---------------------------------------------
type Gettable interface {
Get() int64
}
type MyFunction func(int64) (Gettable, error)
So you need replace MyStruct by Gettable.
//main/sub
//---------------------------------------------
type MyStruct struct {
Val int64
}
func (v MyStruct) Get() int64 {
return v.Val
}
//this signature of TestFn2 is different of MyStruct
//-[TestFn2] func (a int64) (MyStruct, error)
//-[MyFunction] func(int64) (Gettable, error)
func TestFn2(a int64) (Gettable, error) {//<-- replace by Gettable here
return MyStruct{a}, nil
}
Running your code:
//main.go
//---------------------------------------------
func main() {
var f MyFunction
f = TestFn2
a, _ := f(1)
fmt.Println(a)
}
Result is:
{1}
See in playground: https://play.golang.org/p/sRsXix8E_83
As per the Go's assignability rules a function f is assignable to a variable v only if the variable's type T exactly matches the f's signature.
The ability to assign a more specific type in some other languages called "covariance", and Go's type system does not have it.

Effectively wrapping public sdk types in golang

I am using pagerduty go sdk to do a bunch of api requests.
Particularly I am making use of
func NewClient(authToken string) *Client
to create a new Client type. I want to add some utility functions to my own work to *Client. I tried doing this:
type BetterPdClient *pagerduty.Client
func NewClient(auth string) BetterPdClient {
return pagerduty.NewClient(auth)
}
func (b *BetterPdClient) DoSomething() {
b.GetIncident(....)
}
func main() {
pd_client := NewClient("")
fmt.Println(pd_client)
pd_client.DoSomething()
}
But I get the following error:
invalid receiver type *BetterPdClient (BetterPdClient is a pointer type)
I understand that DoSomething() is expecting a pointer as caller. Only other way I could think of is sending the ptr as a function argument:
func NewClient(auth string) *pagerduty.Client {
return pagerduty.NewClient(auth)
}
func DoSomething(cl *pagerduty.Client) {
fmt.Println(cl)
}
func main() {
pd_client := NewClient("")
fmt.Println(pd_client)
DoSomething(pd_client)
}
Is there a better way?
Declaring a type as a pointer to another type is almost never what you want because Go doesn't allow you to add methods to that new type, nor to the pointer of that type as you've already figured out yourself. This one doesn't compile either:
type T struct{}
type P *T
func (P) M() {}
If you want to "extend" a type without "hiding" it's existing functionality your best bet is to embed it in a struct.
type T struct{
// ...
}
func (T) M() {}
type U struct {
*T
}
func NewU() *U {
return &U{&T{}}
}
func (U) N() {}
func main() {
u := NewU()
u.M()
u.N()
}
And what I mean by "hiding existing functionality" is that when you define a new type in terms of another, already existing type, your new type will not get direct access to the methods of the existing type. All you're doing is just saying that your new type should have the same structure as the already existing type. Although it's worth pointing out that this property gives you the ability to convert one type to the other...
type T struct{
// ...
}
func (T) M() {}
type U T
func NewU() *U {
return &U{}
}
func (U) N() {}
func main() {
u := NewU()
u.M() // compile error
u.N()
// convert *U to *T and then call M
(*T)(u).M()
}

Golang base struct to define methods in substructs

I would like to create a base struct which have to method, I want to use these methods in the substructs. For example:
type Base struct {
Type string `json:"$type"`
}
func (b Base) GetJSON() ([]byte, error) {
return json.Marshal(b)
}
func (b Base) SetType(typeStr string) interface{} {
b.Type = typeStr
return b
}
In the new struct I want to use it like this:
type Auth struct {
Base
Username
Password
}
and call these methods in the main:
func main() {
a := Auth{
Username: "Test",
Password: "test",
}
a = a.SetType("testtype").(Auth)
j, _ := a.GetJSON()
}
In the SetType case I got a panic caused by interface{} is not Auth type, it is Base type.
In the GetJSON case I got a json about the Type, but only the Type.
Is there any solution for the problem what I want to solve?
As mentioned in the comments, embedding is not inheritance but composition, so you'll probably have to either:
Re-think your design to use the tools Go has available
Resort to extensive hacking to get the results you want
In the particular case you are showing (trying to get GetJSON() to include also the fields of the outer struct, here is a possible way of getting that to work that does not require many changes (just storing a pointer to the outer struct in Base when creating the struct):
package main
import (
"encoding/json"
"fmt"
)
type Base struct {
Type string `json:"$type"`
selfP interface{} // this will store a pointer to the actual sub struct
}
func (b *Base) SetSelfP(p interface{}) {
b.selfP = p
}
func (b *Base) GetJSON() ([]byte, error) {
return json.Marshal(b.selfP)
}
func (b *Base) SetType(typeStr string) {
b.Type = typeStr
}
type Auth struct {
Base
Username string
Password string
}
func main() {
a := &Auth{
Username: "Test",
Password: "test",
}
a.SetSelfP(a) // this line does the trick
a.SetType("testtype")
j, _ := a.GetJSON()
fmt.Println(string(j))
}
Playground link: https://play.golang.org/p/npuy6XMk_t

Why does method signature have to perfectly match interface method

I began to learn Go and have trouble understanding the following:
package main
import "fmt"
type I interface {
foo(interface {})
}
type S struct {}
func (s S) foo(i int) {
fmt.Println(i)
}
func main() {
var i I = S{}
i.foo(2)
}
This fails with:
cannot use S literal (type S) as type I in assignment:
S does not implement I (wrong type for foo method)
have foo(int)
want foo(interface {})
I don't understand why Go doesn't accept the foo(int) signature given the fact that int implements interface {}. Can anyone help with an explanation?
I think your understanding of interface isn't sound. Interface{} itself is a type. It consists of two things : underlying type and underlying value.
Golang doesn't have overloading. Golang type system matches by name and requires consistency in the types
So, when you are defining a function taking a interface type as a parameter:
foo(interface {})
This is a different function from a function taking int type:
foo(int)
So you should change the following line to
func (s S) foo(i interface{}) {
fmt.Println(i)
}
Or better yet to this:
type I interface {
foo()
}
type S struct {
I int
}
func (s S) foo() {
fmt.Println(s.I)
}
func main() {
var i I = S{2}
i.foo()
}
The error message itself is self-explaining:
"S does not implement I (wrong type for foo method)"
so S{} which is of S type cannot be used on the RHS of type I variable assignment.
To implement I interface, type S needs to define foo function with the exact same signature.
To achieve what you wanted, you can use empty interface as input parameter for your foo function in S and do type assertion
package main
import "fmt"
type I interface {
foo(interface {})
}
type S struct {}
func (s S) foo(i interface{}) {
if i, ok := i.(int); ok{
fmt.Println(i)
}
}
func main() {
var i I = S{}
i.foo(2)
i.foo("2")
}
Try it in go playground

Resources