Практическое применение паттерна Observer

задняя часть Go Шаблоны проектирования
Практическое применение паттерна Observer

предисловие

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

Недавно случайно встретил одного на работе.观察者模式Сцена решения проблемы доступна для всех.

Фон выглядит следующим образом:

Есть несколько дополнительных вещей, которые необходимо сделать в стандартном процессе, когда пользователь завершил создание заказа:

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

Если логика прямо прописана в бизнесе заказа, то это”坨“Бизнесы, которые не являются очень основными, будут занимать все больше и больше, и модификация также может повлиять на обычный процесс заказа.

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

Шаблон наблюдателя

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

Таким образом, издатель и получатель события могут быть полностью отделены друг от друга, не влияя друг на друга, что по сути также является реализацией принципа открытия-закрытия.

образец кода

Давайте в общих чертах рассмотрим интерфейсы и отношения, используемые шаблоном наблюдателя:

  • Основной интерфейс: определяет реализацию регистрации, круговой интерфейс уведомления.
  • Интерфейс наблюдателя: определяет интерфейс для получения уведомлений от субъекта.
  • И субъект, и интерфейс наблюдателя могут иметь несколько реализаций.
  • Бизнес-код должен использовать только для использованияSubject.Nofity()интерфейс.

Рассмотрим пример реализации в процессе создания заказа.

Код реализован на go, и другие языки аналогичны.

Во-первых, два интерфейса определены в соответствии с приведенным выше рисунком:

type Subject interface {
	Register(Observer)
	Notify(data interface{})
}

type Observer interface {
	Update(data interface{})
}

Поскольку это событие, которое размещает заказ, мы определяемOrderCreateSubjectвыполнитьSubject:

type OrderCreateSubject struct {
	observerList []Observer
}

func NewOrderCreate() Subject {
	return &OrderCreateSubject{}
}

func (o *OrderCreateSubject) Register(observer Observer) {
	o.observerList = append(o.observerList, observer)
}
func (o *OrderCreateSubject) Notify(data interface{}) {
	for _, observer := range o.observerList {
		observer.Update(data)
	}
}

один из нихobserverListСрезы используются для хранения всех наблюдателей, подписанных на событие упорядочения.

Следующим шагом является написание бизнес-логики наблюдателя Здесь я реализовал два:

type B1CreateOrder struct {
}
func (b *B1CreateOrder) Update(data interface{}) {
	fmt.Printf("b1.....data %v \n", data)
}


type B2CreateOrder struct {
}
func (b *B2CreateOrder) Update(data interface{}) {
	fmt.Printf("b2.....data %v \n", data)
}

Он также очень прост в использовании:

func TestObserver(t *testing.T) {
	create := NewOrderCreate()
	create.Register(&B1CreateOrder{})
	create.Register(&B2CreateOrder{})

	create.Notify("abc123")
}

Вывод:

b1.....data abc123 
b2.....data abc123 
  1. Создавать创建订单предметsubject.
  2. Зарегистрируйте все события подписки.
  3. Вызывается, когда требуется уведомлениеNotifyметод.

Таким образом, когда нам нужно изменить реализацию каждого события, это не повлияет друг на друга, даже если мы захотим добавить другие реализации, это очень просто:

  1. Напишите класс реализации.
  2. Зарегистрируйтесь в организации.

Основной процесс не будет изменен.

подходит контейнер

На самом деле мы можем также опустить шаг регистрации событий, то есть использование контейнеров, общий процесс выглядит следующим образом:

  1. Все пользовательские события внедряются в контейнер.
  2. При повторной регистрации событий все события вынимаются из контейнера и регистрируются одно за другим.

Здесь используется контейнерgithub.com/uber-go/dig

В измененном коде всякий раз, когда мы добавляем наблюдателя (подписку на событие), нам нужно использовать толькоProvideФункция может быть зарегистрирована в контейнере.

В то же время, чтобы контейнер поддерживал несколько экземпляров одного и того же объекта, необходимо добавить некоторый код:

Observer.go:

type Observer interface {
	Update(data interface{})
}
type (
	Instance struct {
		dig.Out
		Instance Observer `group:"observers"`
	}

	InstanceParams struct {
		dig.In
		Instances []Observer `group:"observers"`
	}
)

существуетobserverВ интерфейс необходимо добавить две новые структуры для хранения нескольких экземпляров одного и того же интерфейса.

group:"observers"Используется для объявления того же интерфейса.

Возвращается при создании конкретного объекта-наблюдателяInstanceобъект.

func NewB1() Instance {
	return Instance{
		Instance: &B1CreateOrder{},
	}
}

func NewB2() Instance {
	return Instance{
		Instance: &B2CreateOrder{},
	}
}

На самом деле он один раз заворачивается в Instance.

Таким образом, при регистрации наблюдателя вы можете получить к нему доступ изInstanceParams.InstancesУдалите все объекты-наблюдатели из .

	err = c.Invoke(func(subject Subject, params InstanceParams) {
		for _, instance := range params.Instances {
			subject.Register(instance)
		}
	})

Таким образом, при использовании получайте объект топика прямо из контейнера, а затем уведомляйте:

	err = c.Invoke(func(subject Subject) {
		subject.Notify("abc123")
	})

Для получения дополнительной информации об использовании dig обратитесь к официальной документации:

pkg.go.Dev/go.UB и .org…

Суммировать

Опытные разработчики найдут и опубликуют очень похожую модель подписки, конечно, их идея похожа, мы не путаемся с разницей между ними (за исключением интервью), узнаем, какие идеи важнее.