Избегайте писать слишком много геттеров и сеттеров в Go - PullRequest
0 голосов
/ 30 мая 2019

Я внедряю систему передачи сообщений в Go.Итак, у меня есть общий интерфейс под названием Msg.Интерфейс Msg определяет много общих полей, таких как источник, место назначения, время отправки, время приема и т. Д. Я не могу определить полный список Msg с, поскольку я хочу, чтобы пользователи библиотеки определяли конкретный тип Msg с,

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

Одно из решений, которое я попробовал, - предоставить простой базовый класс, такой как MsgBase, и определяет все общие свойства, методы получения и установки.И для каждого конкретного типа Msg я вставляю указатель на MsgBase.Это решение работает.

Но затем я хочу встроить версию значения MsgBase в конкретные типы Msg.Это связано с тем, что такие Msg создаются слишком много раз при выполнении, а динамическое выделение MsgBase увеличивает затраты на сборку мусора.Я действительно хочу, чтобы все Msg распределялись статически, поскольку они передаются компонентами и никогда не должны использоваться совместно.Если я использую версию значения MsgBase, я не могу использовать сеттеры, определенные в MsgBase.

Интересно, есть ли какое-нибудь простое решение этой проблемы?

РЕДАКТИРОВАТЬ: Добавление образца кода


type Msg interface {
    // Agent is another interface
    Src() Agent
    SetSrc(a Agent)
    Dst() Agent
    SetDst(a Agent)

    ... // A large number of properties
}

type MsgBase struct {
    src, dst Agent
    ... // Properties as private fields.
}

func (m MsgBase) Src() Agent {
    return m.src
}

func (m *MsgBase) SetSrc(a Agent) {
    m.src = a
}

... // Many other setters and getters for MsgBase


type SampleMsg struct {
    MsgBase // option1
    *MsgBase // option2
}

1 Ответ

2 голосов
/ 30 мая 2019

Помните, что Go не имеет объектно-ориентированного наследования так же, как Java. Похоже, вы пытаетесь написать абстрактный базовый класс, который инкапсулирует все части «сообщения»; это не совсем типичный стиль Go.

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

type MessageMeta struct {
  Source Agent
  Destination Agent
}

Более объектно-ориентированный подход заключается в том, что сообщение имеет (изменяемый) блок метаданных и (неизменяемый, кодированный) полезную нагрузку.

import "encoding"

type Message interface {
  encoding.BinaryMarshaler // requires MarshalBinary()
  Meta() *MessageMeta
}

type SomeMessage struct {
  MessageMeta
  Greeting string
}

func (m *SomeMessage) Meta() *MessageMeta {
  return &m.MessageMeta
}

func (m *SomeMessage) MarshalBinary() ([]byte, error) {
  return []byte(m.Greeting), nil
}

Более процедурный подход, при котором эти две вещи рассматриваются отдельно, также является разумным. В этом случае нет интерфейса для того, что является «сообщением», вы просто передаете кодированную полезную нагрузку; интерфейсы стандартной библиотеки, такие как encoding.BinaryMarshaler, могут иметь здесь смысл. Вы можете включить это в интерфейс более низкого уровня, который является частью вашей библиотеки.

func Deliver(meta *MessageMeta, payload []byte) error { ... }

Переводить один на другой легко

func DeliverMessage(m Message) error {
  payload, err := m.Payload()
  if err != nil {
    return err
  }
  meta := m.Meta()
  return Deliver(meta, payload)
}

Если одно из полей метаданных «доставлено», то при передаче указателя на объект метаданных по всей цепочке можно обновить это поле в исходном объекте.

Я бы не стал беспокоиться о сборке мусора как о первоклассном подходе, за исключением того, чтобы не быть чрезмерно расточительным и проверять распределение объектов, если GC начинает появляться в профилях. Создание двух объектов вместо одного, вероятно, не будет большой проблемой здесь.

...