I want to replicate the following Java code in Golang in the most idiomatic way possible:
public class Handler {
private Storage storage;
private Mapper mapper;
public Handler(Storage storage, Mapper mapper) {
this.storage = storage;
this.mapper = mapper;
}
public void handleKey(String k) {
storage.put(k, mapper.map(k));
}
}
interface Storage {
public void put(String k, String v);
public String get(String k);
}
@FunctionalInterface
interface Mapper {
public String map(String a);
}
private static class InMemoryStorage implements Storage {
private Map<String, String> table;
public InMemoryStorage() {
table = new HashMap<>();
}
public void put(String k, String v) {
table.put(k, v);
}
public String get(String k) {
return table.get(k);
}
}
Handler
class only exposes the method handleKey
. The behaviour of this class is parametrized by passing different concrete Storage
and Mapper
implementations.
After reading Effective Go - Embedding, I thought this would be a good use of embedding interfaces intro structs. But I can't figure out how to avoid exposing the embedded interfaces' methods to Handler
users. I can do something like
type Handler struct {
store Storage
mapper Mapper
}
func (h Handler) Handle(k string) {
h.store.Put(k, h.mapper.Map(k))
}
type Storage interface {
Put(k string, v string)
Get(k string) string
}
type Mapper interface {
Map(k string) string
}
type inMemoryStorage {
table map[string]string
}
func NewInMemoryStorage() Storage {
return &inMemoryStore{table: make(map[string]string)}
}
but then I cannot pass concrete implementations to the Handler
(creating struct literal) because store
and mapper
are unexported. And I do not want to create factory methods for each possible combination... Any suggestions?
Those are not embedded; embedding has a specific meaning in Go, as outlined in the spec, and as explained in the Effective Go section you linked to. It refers to unnamed fields, whose fields and methods are accessible implicitly from their containing type. Your fields are named, not embedded.
That said, your two struct fields, store
and mapper
, are not exported, so they will not be exposed to any user outside the package in which Handler
is defined; you seem to already have your desired behavior in that regard.
I'm not sure what you mean when you say you would have to "create factory methods for each possible combination" - I don't see any reason that would be necessary. You need only one factory method:
func NewHandler(s Storage, m Mapper) Handler {
return Handler{store: s, mapper: m}
}
Which could be used with any combination of implementations of Storage
and Mapper
by passing appropriate values to the function.