Нарушение в Голанге нарушает закон Деметры?
Это то, что Эффективный ГО должен был сказать о встраивании в Голанг
Когда мы встраиваем тип, методы этого типа становятся методами внешнего типа, но когда они вызываются, получатель метода является внутренним типом, а не внешним
У меня был фрагмент кода, где я имел Struct User
определяется следующим образом
type User struct {
Name string
Password string
*sql.Tx
}
А потом я звоню u.Query("some query here")
и т.д. Я сделал это специально, чтобы я мог избежать звонков, как u.Transaction.Query("query")
, что явно нарушает закон Деметры. Теперь, после прочтения документации и эффективной работы, я сомневаюсь и в достоинствах первого подхода. Я нарушаю Закон Деметры? Если да, то как я могу избежать этого?
1 ответ
Концепция встраивания несколько нарушает Закон Деметры, поскольку не скрывает, что тип был внедрен, если экспортируется сам тип. Обратите внимание, что встраивание неэкспортированного типа не нарушает 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()
клиенты, использующие это, не затрагиваются, если внутренние 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.