Каков наилучший способ скрыть структурные поля и в то же время заставить его синхронизировать доступ и изменение полей? - PullRequest
0 голосов
/ 27 сентября 2018

Вот проблема, с которой я сталкиваюсь, используя golang struct

 type User struct {
     name  string `json:"name"`
     email string `json:"email"`
 }

Теперь я хочу, чтобы доступ и изменение этих полей структуры были безопасными одновременно, и, следовательно, я добавил мьютекс и добавил методы, которые блокируютmutex Пользовательский код теперь может получать доступ и мутировать только через методы и не может напрямую обращаться к полям

type User struct {
     name string  `json:"name"`
     email string `json:"email"`
     sync.RWMutex `json:"-"`
}

func (u *User) Name() string {
   u.RLock()
   defer u.RUnlock()

   return u.name  
}

func (u *User) Email() string {
   u.RLock()
   defer u.RUnlock()

   return u.email  
}

func (u *User) SetName(p string) {
   u.Lock()
   defer u.Unlock()

   u.name = p  
}

func (u *User) SetEmail(p string) {
   u.RLock()
   defer u.RUnlock()

   u.email = p  
}

Пока все хорошо, но проблема в том, что json / bson marshalling терпит неудачу, так как требует экспортируемых полей

Таким образом, я реализую пользовательский маршаллинг, который возвращает похожую структуру, но с экспортированными полями

func (self User) MarshalJSON() ([]byte, error) {
    var usr struct {
        Name  string `json:"name"` 
        Email string `json:"email,omitempty"`
        sync.RWMutex `json:"-"`
    }
    return json.Marshal(usr)
}

func (self *User) UnmarshalJSON(b []byte) error {
    var usr struct {
        Name   string  `json:"name"`
        Email  string  `json:"email"` 
        sync.RWMutex   `json:"-"`
    }

    if err := json.Unmarshal(b, &usr); err != nil {
        return err
    }

    self.name = usr.Name
    self.email = usr.Email

    return nil
}

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

MyВопрос в том, как заставить код маршаллинга использовать тот же мьютекс?Создание глобального мьютекса не решит проблему, так как мы создаем несколько экземпляров структуры.Пользовательская структура, объявленная в маршалинге, отличается от основной пользовательской структуры, поэтому блокировка мьютекса внутренней структуры не имеет смысла.

Какой лучший способ добиться этого?

1 Ответ

0 голосов
/ 27 сентября 2018

Вам не нужно добавлять мьютекс к значениям, которые вы используете, это бессмысленно.

Но вам нужно использовать мьютекс User при копировании или установке его полей.

Некоторые важные вещи:

  • Если вы встраиваете мьютекс без указателя, вы должны указать все свои методы с приемником указателя, иначе блокировка будет скопирована!
  • Вам не нужно указывать json теги для неэкспортированных полей, это избыточно.Более того, поскольку вы предоставляете свою собственную логику маршалинга, вам даже не нужно предоставлять какие-либо теги json, поскольку они вообще не будут использоваться.Так что этого User вполне достаточно:

    type User struct {
        name  string
        email string
        sync.RWMutex
    }
    
  • Даже если name и email не экспортированы, эти значения не являются "безопасными", так как вы предоставили экспортированный MarshalJSON() метод, который возвращает эти значения (в формате JSON).У вас все еще есть безопасность во время компиляции для доступа к User.name и User.email, но вы знаете, что значения, которые они хранят, не являются секретными.

Пример:

func (u *User) MarshalJSON() ([]byte, error) {
    u.RLock()
    usr := struct {
        Name  string `json:"name"`
        Email string `json:"email,omitempty"`
    }{u.name, u.email}
    u.RUnlock()

    return json.Marshal(usr)
}

func (u *User) UnmarshalJSON(b []byte) error {
    usr := struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }{}

    if err := json.Unmarshal(b, &usr); err != nil {
        return err
    }

    u.Lock()
    u.name = usr.Name
    u.email = usr.Email
    u.Unlock()

    return nil
}
...