Junior — Senior
Decorator with merging simultaneous client requests
livecode
Task condition
It is necessary to create a wrapper-decorator around the Client interface that will guarantee that when concurrent calls to the GetUser(name) method are made:
- if the provided names match (for example, "Vasya"), the actual implementation Client.GetUser("Vasya") will be executed only once;
- if the names differ ("Vasya" and "Oleg"), the requests are processed independently.
The Client.GetUser method is resource-intensive and costly, so it is important that requests with the same parameter are executed only once, and the obtained result is shared among all goroutines making the request.
type User struct {
Id int64
}
type IClient interface {
GetUser(name string) (*User, error)
}
type Client struct {}
func (c Client) GetUser(name string) (*User, error) {
// resource-intensive operation
return &User{}, nil
}
func NewClient() *Client {
return &Client{}
}
type Decorator struct {
client IClient
// ...
}
func (d *Decorator) GetUser(name string) (*User, error) {
// ...
}
func NewDecorator(c IClient) *Decorator {
return &Decorator{client: c}
}
Example usage:
func main() {
client := NewClient()
decoratedClient := NewDecorator(client)
go func() {
vasya, err := decoratedClient.GetUser("Vasya")
// ...
}()
go func() {
vasya, err := decoratedClient.GetUser("Vasya")
// ...
}()
go func() {
vasya, err := decoratedClient.GetUser("Vasya")
// ...
}()
go func() {
vasya, err := decoratedClient.GetUser("Vasya")
// ...
}()
go func() {
oleg, err := decoratedClient.GetUser("Oleg")
// ...
}()
go func() {
oleg, err := decoratedClient.GetUser("Oleg")
// ...
}()
}
The task requires implementing request synchronization, caching the result during execution, and correctly distributing errors/results among all callers.