snapshot of https://github.com/uber-go/guide.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
132
src/embed-public.md
Normal file
132
src/embed-public.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Avoid Embedding Types in Public Structs
|
||||
|
||||
These embedded types leak implementation details, inhibit type evolution, and
|
||||
obscure documentation.
|
||||
|
||||
Assuming you have implemented a variety of list types using a shared
|
||||
`AbstractList`, avoid embedding the `AbstractList` in your concrete list
|
||||
implementations.
|
||||
Instead, hand-write only the methods to your concrete list that will delegate
|
||||
to the abstract list.
|
||||
|
||||
```go
|
||||
type AbstractList struct {}
|
||||
|
||||
// Add adds an entity to the list.
|
||||
func (l *AbstractList) Add(e Entity) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Remove removes an entity from the list.
|
||||
func (l *AbstractList) Remove(e Entity) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>
|
||||
|
||||
```go
|
||||
// ConcreteList is a list of entities.
|
||||
type ConcreteList struct {
|
||||
*AbstractList
|
||||
}
|
||||
```
|
||||
|
||||
</td><td>
|
||||
|
||||
```go
|
||||
// ConcreteList is a list of entities.
|
||||
type ConcreteList struct {
|
||||
list *AbstractList
|
||||
}
|
||||
|
||||
// Add adds an entity to the list.
|
||||
func (l *ConcreteList) Add(e Entity) {
|
||||
l.list.Add(e)
|
||||
}
|
||||
|
||||
// Remove removes an entity from the list.
|
||||
func (l *ConcreteList) Remove(e Entity) {
|
||||
l.list.Remove(e)
|
||||
}
|
||||
```
|
||||
|
||||
</td></tr>
|
||||
</tbody></table>
|
||||
|
||||
Go allows [type embedding] as a compromise between inheritance and composition.
|
||||
The outer type gets implicit copies of the embedded type's methods.
|
||||
These methods, by default, delegate to the same method of the embedded
|
||||
instance.
|
||||
|
||||
[type embedding]: https://go.dev/doc/effective_go#embedding
|
||||
|
||||
The struct also gains a field by the same name as the type.
|
||||
So, if the embedded type is public, the field is public.
|
||||
To maintain backward compatibility, every future version of the outer type must
|
||||
keep the embedded type.
|
||||
|
||||
An embedded type is rarely necessary.
|
||||
It is a convenience that helps you avoid writing tedious delegate methods.
|
||||
|
||||
Even embedding a compatible AbstractList *interface*, instead of the struct,
|
||||
would offer the developer more flexibility to change in the future, but still
|
||||
leak the detail that the concrete lists use an abstract implementation.
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>
|
||||
|
||||
```go
|
||||
// AbstractList is a generalized implementation
|
||||
// for various kinds of lists of entities.
|
||||
type AbstractList interface {
|
||||
Add(Entity)
|
||||
Remove(Entity)
|
||||
}
|
||||
|
||||
// ConcreteList is a list of entities.
|
||||
type ConcreteList struct {
|
||||
AbstractList
|
||||
}
|
||||
```
|
||||
|
||||
</td><td>
|
||||
|
||||
```go
|
||||
// ConcreteList is a list of entities.
|
||||
type ConcreteList struct {
|
||||
list AbstractList
|
||||
}
|
||||
|
||||
// Add adds an entity to the list.
|
||||
func (l *ConcreteList) Add(e Entity) {
|
||||
l.list.Add(e)
|
||||
}
|
||||
|
||||
// Remove removes an entity from the list.
|
||||
func (l *ConcreteList) Remove(e Entity) {
|
||||
l.list.Remove(e)
|
||||
}
|
||||
```
|
||||
|
||||
</td></tr>
|
||||
</tbody></table>
|
||||
|
||||
Either with an embedded struct or an embedded interface, the embedded type
|
||||
places limits on the evolution of the type.
|
||||
|
||||
- Adding methods to an embedded interface is a breaking change.
|
||||
- Removing methods from an embedded struct is a breaking change.
|
||||
- Removing the embedded type is a breaking change.
|
||||
- Replacing the embedded type, even with an alternative that satisfies the same
|
||||
interface, is a breaking change.
|
||||
|
||||
Although writing these delegate methods is tedious, the additional effort hides
|
||||
an implementation detail, leaves more opportunities for change, and also
|
||||
eliminates indirection for discovering the full List interface in
|
||||
documentation.
|
||||
Reference in New Issue
Block a user