
本文介绍通过结构体嵌入(embedding)和组合(composition)消除接口方法重复实现的 DRY(Don't Repeat Yourself)技巧,以 getName() 为例,展示如何将通用字段与行为统一抽取到共享类型中,提升代码可维护性与扩展性。
本文介绍通过结构体嵌入(embedding)和组合(composition)消除接口方法重复实现的 dry(don't repeat yourself)技巧,以 `getname()` 为例,展示如何将通用字段与行为统一抽取到共享类型中,提升代码可维护性与扩展性。
在 Go 中,当多个结构体需实现同一接口且存在共用字段(如 name string)及对应方法(如 getName() string)时,直接为每个结构体单独实现会导致冗余代码,违背 DRY 原则。上述示例中,rect 和 circle 均需重复定义 name 字段并实现相同的 getName() 方法——这不仅增加维护成本,也削弱了类型抽象的清晰性。
推荐解法:使用嵌入(Embedding)提取通用能力
核心思路是将“命名能力”从具体几何类型中解耦,封装为独立的、可复用的组合单元。我们定义一个 namedGeometry 结构体,嵌入 geometry 接口(注意:此处应为嵌入具体实现基类或使用字段组合;由于 Go 接口不能直接嵌入,更合理的方式是嵌入一个含 name 的基础结构体,并让 rect/circle 嵌入它),但更符合 Go 惯用法且真正 DRY 的方案如下:
✅ 正确且 idiomatic 的 DRY 实现:
// 定义一个通用的命名基础结构体
type named struct {
name string
}
func (n named) getName() string {
return n.name
}
// geometry 接口保持不变(不含 getName,使其更专注几何语义)
type geometry interface {
area() float64
perim() float64
}
// rect 和 circle 嵌入 named,自动获得 getName 行为
type rect struct {
named // 嵌入:继承字段与方法
width, height float64
}
type circle struct {
named // 嵌入:复用 getName()
radius float64
}
// 实现 geometry 接口方法(area/perim)——与之前一致
func (r rect) area() float64 { return r.width * r.height }
func (r rect) perim() float64 { return 2*r.width + 2*r.height }
func (c circle) area() float64 { return math.Pi * c.radius * c.radius }
func (c circle) perim() float64 { return 2 * math.Pi * c.radius }
// 现在 geometry 接口无需包含 getName();
// 若需统一访问名称,可定义新接口或直接调用 r.getName()
type namedGeometry interface {
geometry
getName() string
}⚠️ 注意:原答案中
type namedGeometry struct { geometry; name string }存在概念混淆——geometry是接口,不能作为字段嵌入(除非指针或具体类型);且getName()在接口中已声明,却未被namedGeometry自动满足。因此,嵌入具体结构体named是更安全、更清晰的实践。
优势总结:
- ✅ 零重复:
getName()仅实现一次,所有嵌入named的类型自动获得; - ✅ 职责分离:
named专注标识逻辑,rect/circle专注几何逻辑; - ✅ 易扩展:新增
triangle或polygon时,只需嵌入named即可支持命名; - ✅ 类型安全:仍完全兼容
geometry接口,measure()函数可无缝配合新类型(若需getName(),则改用namedGeometry接口)。
最终,DRY 不仅关乎减少代码行数,更是关于通过组合构建高内聚、低耦合的类型体系——这是 Go “组合优于继承”哲学的典型落地。

















