Нарушение в Голанге нарушает закон Деметры?

Это то, что Эффективный ГО должен был сказать о встраивании в Голанг

Когда мы встраиваем тип, методы этого типа становятся методами внешнего типа, но когда они вызываются, получатель метода является внутренним типом, а не внешним

У меня был фрагмент кода, где я имел 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.

Другие вопросы по тегам