4

这就是Effective GO关于在 golang 中嵌入的内容

当我们嵌入一个类型时,该类型的方法成为外部类型的方法,但是当它们被调用时,方法的接收者是内部类型,而不是外部类型

我有一个代码片段,我Struct User定义如下

type User struct {
    Name     string
    Password string
    *sql.Tx
}

然后我打电话u.Query("some query here")等。我专门这样做是为了避免像这样的电话u.Transaction.Query("query"),这显然违反了德墨忒耳法则。现在在阅读了文档和有效的 go 之后,我也怀疑第一种方法的优点。我违反了得墨忒耳法则吗?如果是,我该如何避免?

4

1 回答 1

6

嵌入概念在某种程度上违反了得墨忒耳法则,因为它没有隐藏如果类型本身被导出,则类型被嵌入的事实。请注意,嵌入未导出的类型不会违反 LoD(您不能引用未导出的字段和方法)。

但这并不强制您以同样违反 LoD 的方式引用提升的字段或方法。嵌入本身只是一种技术,因此您可以将通用的共享代码“外包”给其他类型;或者从另一个角度来看,在创建新类型时使用其他类型。您引用嵌入类型的提升字段或方法的方式可能违反法律。

正如您所说,如果您将其称为u.Tx.Query(),那显然违反了德米特法则:您正在使用User嵌入的实现细节*sql.Tx

但如果你这样称呼它:u.Query()没关系。这种形式不会暴露或利用*sql.Tx嵌入的事实。*sql.Tx如果实现更改并且不再嵌入(例如,将其更改为“常规”字段或完全删除,并添加方法),此表单将继续工作User.Query()

如果您不想允许访问导出的嵌入类型的字段值,请将其设为未导出的常规字段并添加一个User.Query()可以委托给该字段的“显式”方法,例如:

type User struct {
    Name     string
    Password string
    tx       *sql.Tx // regular field, not embedded; and not exported
}

func (u *User) Query(query string, args ...interface{}) (*sql.Rows, error) {
    return u.tx.Query(query, args...)
}

进一步说明:

在示例中,如果u.Query()使用了,则使用 this 的客户端不受影响,如果内部发生了User变化(无论是u.Query()表示提升的方法还是表示 的方法User,即:User.Query())。

如果sql.Tx更改,是的,u.Query()可能不再有效。sql.Tx但不太可能发生不相容的情况。如果您是已更改包的开发人员,并且进行了不兼容的更改,那么您有责任更改取决于您的不兼容更改的其他代码。这样做(正确更新u.Query())调用的客户端u.Query()不会受到影响,客户端仍然不需要知道引擎盖下的更改。

这正是 LoD 所保证的:如果您使用u.Query()而不是u.Tx.Query(),如果在User内部发生更改,则客户端调用u.Query()不需要知道或担心这一点。LoD并不是一件坏事。你不应该放弃它。你可以选择你遵循的原则,但你也应该思考而不是遵循所选择的原则所规定的一切,不惜一切代价。

还有一件事要明确:LoD 不涉及 API 不兼容的更改。它提供的是,如果遵循,实体的内部更改将不会影响使用实体“公共”面的其他实体。如果sql.Tx以剧烈的方式更改而Tx.Query()不再可用,则 LoD 不会“覆盖”。

于 2015-10-20T10:46:36.833 回答