I have my struct defining a Subscribers that map channels.
package ws
type SessionHandler struct {
Subscribers map[chan interface{}]bool
}
I wanna make it possible to instantiate it with any kind of channel, like this:
type WsSession struct {
handler *ws.SessionHandler
}
handler := &ws.SessionHandler{
Subscribers: make(map[chan WsResponse]bool),
}
The code example I provided doesn't work (cannot use make(map[chan WsResponse]bool) (value of type map[chan WsResponse]bool) as map[chan interface{}]bool value in struct literal), but how could I update it to my purposes?
What I was trying to accomplish will be possible in Go v1.18, as commented by #torek in my question.
For now, what I ended doing was this:
handler := &ws.SessionHandler{
Subscribers: make(map[chan json.RawMessage]bool),
}
I stopped relying on a go struct and communicated json.RawMessage in my channels. It's not totally clean because I need to marshal/unmarshal my message to give the proper treatment, but its "generic" and I could accomplish what I was trying to do.
Related
I'm very new to golang and the use of interfaces more generally. I've stubbed out some code here:
type Alerter interface {
Alert()
}
type AlertConfig struct{}
type Alert struct {
Config *AlertConfig
}
type AlertConfigurator interface {
UpdateConfig(key, value interface{}) (*AlertConfig, error)
}
type EmailAlertConfig AlertConfig {
Recipients []mail.Address,
}
type HTTPAlertConfig AlertConfig {
Method string
TargetURL url.URL
}
type HTTPAlert struct {
Config *HTTPAlertConfig
}
type EmailAlert struct {
Config *EmailAlertConfig
}
func (ea *EmailAlert) Alert() {
// this would actually send an email using data in its Config
return
}
func (ha *HTTPAlert) Alert() {
// this would actually hit an HTTP endpoint using data in its Config
return
}
I'm sure I have all kinds of assumptions & biases that are hobbling my ability to express what I want to accomplish with this:
I want to create different types of "Alert"s. Any alert I create should have an "Alert()"
method that triggers the Alert to actually do something (send an email, or POST to a URL,
for example.
The trouble comes in representing an Alert's "Config". Different Alerts have different fields
in their Configs. However, for each Alert type, specific fields are required to be there.
To accomplish that I wanted to create a base type "AlertConfig" as a struct with arbitrary fields,
then define, say, EmailAlertConfig as type 'AlertConfig', but having these specific fields, and then
type 'HTTPAlertConfig' as type 'AlertConfig' requiring different fields. This way, I can define
the 'Alert' type as having a field 'Config *AlertConfig'.
I can almost emulate what I want if AlertConfig is defined as map[string]interface{}, but
in this case I can't leverage golang's checking to validate that an EmailConfig has the required fields.
It seems pretty clear that I'm thinking about this the wrong way. I could use a hand in getting on the right track & appreciate your advice.
Declare a type with the common fields:
type AlertConfig struct {
ExampleCommonField string
}
Embed that type in actual configurations:
type HTTPAlertConfig struct {
AlertConfig
Method string
TargetURL url.URL
}
Based on the code in the question, the alert and config types can be combined.
func (ha *HTTPAlertConfig) Alert() {
// this will hit an HTTP endpoint using data in the receiver
return
}
One way to deal with this problem is to leave configs as purely implementation specific:
type HTTPAlert struct {
Config *HTTPAlertConfig
}
func (a *HTTPAlert) Alert() {...}
type EmailAlert struct {
Config *EmailAlertConfig
}
func (e *EmailAlert) Alert() {...}
With this implementation, the actual Alert implementation can use the type-specific alert configuration, leaving the problem of initializing the config.
When you create the instance of an alert, you can perform type-specific initialization. For instance:
var alerts = map[string]func(configFile string) Alert {
"htmlAlert": NewHTMLAlert,
"emailAlert" NewEmailAlert,
}
func NewHTMLAlert(configFile string) Alert {
var alert HTMLAlert
// Read file, initialize alert
return &alert
}
...
Can I define an interface with a channel in Go? I would like to define an interface that would allow me to use objects of different types that would all have the same channel defined. EXE:
type I interface {
chan Communications []byte
otherMethod()
}
(This give syntax error: unexpected token chan. I tried a few different syntaxes and some googling with no avail.)
An interface does not hold data, it defines what something implements.
You could have a method that returns a channel. eg:
type I interface {
getChannel() (chan []byte)
otherMethod()
}
Please read up on interfaces. The tour would help.
I'm getting the following error:
./main.go:31: cannot use telegramService (type messaging.TelegramService) as type mypackage.MessagingService in argument to mypackage.RegisterMessagingService:
messaging.TelegramService does not implement mypackage.MessagingService (wrong type for HandleIncomingMessage method)
have HandleIncomingMessage(telegram.Message) error
want HandleIncomingMessage(mypackage.IncomingMessage) error
I have an interface that describes a messaging service like Telegram or WhatsApp, and an interface that describes an incoming message from one of those services:
// IncomingMessage is a message that comes in on a messaging service
type IncomingMessage interface {
Send() error
}
// MessagingService is a service on which messages can be send (like Telegram or FB Messenger)
type MessagingService interface {
Start()
HandleIncomingMessage(IncomingMessage) error
GetHTTPHandler() http.HandlerFunc
GetCommands() []MessagingCommand
}
The first implementation of MessagingService is for Telegram. The issue is the HandleIncomingMessage function, which currently doesn't really do anything and just looks like this:
// HandleIncomingMessage will take an incoming message and repond to it
func (s TelegramService) HandleIncomingMessage(msg *telegram.Message) error {
return nil
}
The issue is that this function accepts a telegram.Message, which the compiler says doesn't comply with the interface. The thing is, that telegram.Message is an implementation of IncomingMessage:
// Message is a Telegram message
type Message struct {
// Added the line below at some point, but it didn't work without it either
mypackage.IncomingMessage
MessageID uint64 `json:"message_id"`
FirstName string `json:"first_name"`
Username string `json:"username"`
Date uint64 `json:"date"`
Text string `json:"text"`
Chat Chat `json:"chat"`
From User `json:"from"`
}
// Send will take m and send it
func (m Message) Send() error {
// Do stuff
return nil
}
Initially IncomingMessage was an empty interface, which is where I first noticed the issue. I tried adding the function Send() which I was going to add anyway, as I thought maybe just giving it any struct wouldnt't work. However, I'm still getting this error.
I don't see any reason why telegram.Message doesn't implement the interface, it's pretty straight forward.
Can anyone explain why this doesn't work?
PS: My package isn't actually called mypackage, changed for clarity
HandleIncomingMessage must take an IncomingMessage argument since that's the way the interface is defined. You can't define an implementation of HandleIncomingMessage that takes some other type as the argument, even if that type implements IncomingMessage. You can define your function to take IncomingMessage and convert that to *telegram.Message using a type assertion:
func (s TelegramService) HandleIncomingMessage(im IncomingMessage) error {
msg := im.(*telegram.Message)
return nil
}
I'm assuming you actually want to be using a pointer to telegram.Message. If so, you need to change the definition of the Send method to take a pointer receiver.
I'm wondering if this is something that's done in Go or if I'm thinking about it all wrong: composing type x interface and type x struct so my interface methods have access to specific data too:
The C programmer in my wants to do this:
type PluginHandler interface {
onLoad()
pm *PluginManager
}
func (ph PluginHandler) onLoad() {
pm.DoSomething()
}
There I have an interface defined with a function, but also some data I want to pass to those functions but this is a syntax error.
So is this something that's doable in Go through some other method or am I just thinking about the problem wrong?
You have defined onLoad incorrectly. You cannot define a function directly on interface type.
Once you have an interface, you need another type to implement methods specified in the interface. For example, if another type implements onLoad method, they automatically (implicitly) implement the interface PluginHandler.
The other thing you need to do is change the interface function type to accept the required data:
type PluginHandler interface {
onLoad(*PluginManager)
}
struct SomeType {
// ...
}
func (s SomeType) onLoad(pm *PluginManager) { // SomeType now implements
pm.DoSomething() // PluginHandler interface.
}
This way, you get to inject whichever PluginManager required by PluginHandler.
Also, you can use SomeType as a PluginHandler type whereever required.
func someFuntion(ph PluginHandler) {
// ...
ph.onLoad(pm)
// ...
}
Can be called with an input argument of type SomeType:
s := SomeType{}
someFunction(s)
TL;DR; There is no direct translation to Go.
Long answer:
Go interfaces are only methods.
Go structs are only data (with the possibility of receiver methods).
You can reference, and even embed interfaces within structs:
type Frobnicator interface {
Frobnicate() error
}
type Widget struct {
Frobnicator
WidgetName string
}
But that's not really what you're talking about.
The best answer to your dilema is, I believe: Take a step back. You're focusing on the trees, and you need to look at the forest. Go takes a different approach than C, or classical OO languages like C++ and Java.
Look at the general problem to be solved, and find solutions to that in Go. This can be a painful process (I can say from experience), but it's really the only way to learn the new way of thinking.
Just for the record, you can add extra methods to an existing type, by introducing another (indirection) type as:
type HandlerManager PluginManager
func (x *HandlerManager) onLoad() {
((*PluginManager)(x)).DoSomething()
}
And if you need to go with a more generic solution, a combination of Adapter & Strategy patterns could do:
type PluginHandlerAdapter struct{ _onLoad func() }
func (x *PluginHandlerAdapter) onLoad() {
x._onLoad()
}
Used like (public/private access ignored):
type PluginManager struct {
PluginHandlerAdapter
}
func NewPluginManager() *PluginManager {
res := new(PluginManager)
res._onLoad = res.DoSomething
return res
}
I have several different structures.
Here show two:
type AdsResponse struct {
Body struct {
Docs []struct {
ID int `json:"ID"`
// others
} `json:"docs"`
} `json:"response"`
Header `json:"responseHeader"`
}
type OtherResponse struct {
Body struct {
Docs []struct {
ID int `json:"ID"`
// others
} `json:"docs"`
} `json:"response"`
Header `json:"responseHeader"`
}
but i don't know how i can do for this method accepts and return both.
func Get(url string, response Response) (Response, bool) {
res, err := goreq.Request{
Uri: url,
}.Do()
// several validations
res.Body.FromJsonTo(&response)
return response, true
}
And use like this:
var struct1 AdsResponse
var struct2 OtherResponse
Get("someURL", struct1)
Get("someURL", struct2)
There are any form?
Your code example is somewhat confusing since both structs appear to be identical. I'll assume that they differ somewhere in "others".
First, I generally recommend creating a wrapper around these kinds of JSON deserializations. Working directly on the JSON structure is fragile. Most of your program should not be aware of the fact that the data comes down in JSON. So for instance, you can wrap this in an Ads struct that contains an AdsResponse, or just copies the pieces it cares about out of it. Doing that will also make some of the below slightly easier to implement and less fragile.
The most common solution is probably to create an interface:
type Response interface {
ID() int
}
You make both Ads and Others conform to Response. Then you can return Response. If necessary, you can type-switch later to figure out which one you have and unload other data.
switch response := response.(type) {
case Ads:
...
case Other:
...
}
I don't quite get why you have the reponse as a parameter and as a return. I think you dont need to return it. You should pass a pointer to the reponse and fill it with the data. Also, I'd return an Error instead of a boolean, but that is another topic.
Anyway, the solution is to use interface{} (empty interface).
You are lucky because the function you are using (FromJsonTo) accepts an empty interface as a parameter, so you can safely change your parameter type to interface{} and just pass it to FromJsonTo. Like this:
func Get(url string, response interface{}) bool {
res, err := goreq.Request{
Uri: url,
}.Do()
// several validations
res.Body.FromJsonTo(response)
return true
}
Warning: I did not compile the code.
Then you would call this function with the url and a pointer to one of the reponse structs like this:
var struct1 AdsResponse
var struct2 OtherResponse
Get("someURL", &struct1)
Get("someURL", &struct2)
The way to achieve this is through Go's interfaces.
Two options:
empty interface
Get(url string, response interface{}) (Response, bool)
This option allows any value to be given to this function.
custom interface
Creating a custom interface will allow you to narrow down the types that can be provided as arguments to your function.
In this case you'll have to create an interface that all your Response structs will need to abide by. Any struct really that abides by that interface will be able to be used as an argument of your function.
Something like this:
type MyResponse interface {
SomeFunction()
}
Then your function signature could look like
Get(url string, response MyResponse) (MyResponse, bool)
As long as AdsResponse and OtherResponse abide by the MyResponse interface, they will be allowed to be used as arguments to the function.
Follow the solution working at Go Playground
Go has no polymorphic or any other OO like behaviour, so, when you try to pass a AdsResponse or OtherResponse struct as an Response (or any interface{}), these values becomes an Response (or other param type specified), and is not possible to Go to infer the real type that originate these interface{} and correctly decode your json to these struct types as expected.
This kind of thing should works perfectly in OO languages, like Java, C# etc. There is no hierarchy generalization/specialization on structs/interfaces in Go.
You would need to do a type assertion in your Rest executor, or a switch case, but it seems that you need a generic REST executor, like a generic lib some thing like that. Would not reasonable create a switch case for each struct in your program. Maybe you have dozens or hundreds of structs soon.
I think that a reasonable solution is the rest client pass a lambda function to do the last step for your, that is just create a correct struct destination type and call json decode.
As i say above, the return type of executeRest() in my example will became an interface{}, but the rest client can securely do the type assertion of returned value after executeRest() call.