предисловие
предыдущий пост«23 шаблона проектирования с использованием Go для реализации GoF (1)»Представляет 23 шаблона проектирования.творческий режим(Creational Pattern), порождающий шаблон — это тип шаблона проектирования, связанный с созданием объектов.Скрыть особенности создания объекта от пользователя объектаТаким образом, цель развязки. Эта статья в основном посвященаСтруктурный образец(Структурный паттерн), основная идеяСобирайте несколько объектов в более крупные структуры, сохраняя гибкость и эффективность структуры., чтобы решить проблему связи между модулями из структуры программы.
Составной узор
Кратко
В объектно-ориентированном программировании есть два общеми метода дизайна объектов,комбинацияинаследовать, оба могут решить проблему повторного использования кода, но использование последнего чревато побочными эффектами слишком глубокого наследования и слишком сложных отношений между объектами, что приводит к ухудшению сопровождения кода. Таким образом, классический принцип объектно-ориентированного проектирования таков:Композиция над наследованием.
Мы все знаем, что семантика, представленная комбинацией, - это «имеет-а», то есть взаимосвязь между частью и целым. Самый классический комбинированный режим описывается следующим образом:
Объекты сгруппированы в древовидную структуру для представления иерархии «часть-целое», что позволяет пользователям последовательно использовать отдельные объекты и комбинированные объекты.
Язык Go естественным образом поддерживает режим композиции, а поскольку он не поддерживает отношения наследования, Go также преследуетКомпозиция над наследованиемпринципа и призываем всех использовать комбинированный метод в программировании. Есть два способа реализовать шаблон композиции в Go:прямая комбинация(Прямая композиция) иВстроить комбинацию(встраивание композиции), давайте вместе обсудим эти два различных метода реализации.
Перейти к реализации
Реализация Direct Composition похожа на Java/C++, то есть один объект используется как свойство-член другого объекта.
Типичная реализация, такая как«23 шаблона проектирования с использованием Go для реализации GoF (1)»Например,Message
структура, поHeader
иBody
составлен. ТакMessage
представляет собой целое, иHeader
иBody
является частью сообщения.
type Message struct {
Header *Header
Body *Body
}
Теперь давайте рассмотрим немного более сложный пример, также учитывая систему обработки сообщений в стиле плагинов, описанную в предыдущей статье. Ранее мы использовалиАбстрактный заводской узорРешена проблема с загрузкой плагина.Обычно у каждого плагина есть жизненный цикл.Общими являются стартовое состояние и стоповое состояние.Теперь используемКомбинированный режимдля решения проблемы с запуском и остановкой плагина.
сначала дайPlugin
Интерфейс добавляет несколько методов, связанных с жизненным циклом:
package plugin
...
// 插件运行状态
type Status uint8
const (
Stopped Status = iota
Started
)
type Plugin interface {
// 启动插件
Start()
// 停止插件
Stop()
// 返回插件当前的运行状态
Status() Status
}
// Input、Filter、Output三类插件接口的定义跟上一篇文章类似
// 这里使用Message结构体替代了原来的string,使得语义更清晰
type Input interface {
Plugin
Receive() *msg.Message
}
type Filter interface {
Plugin
Process(msg *msg.Message) *msg.Message
}
type Output interface {
Plugin
Send(msg *msg.Message)
}
Для подключаемой системы обработки сообщений все является подключаемой программой, поэтому мыPipeine
Также разработан как плагин, который реализуетPlugin
интерфейс:
package pipeline
...
// 一个Pipeline由input、filter、output三个Plugin组成
type Pipeline struct {
status plugin.Status
input plugin.Input
filter plugin.Filter
output plugin.Output
}
func (p *Pipeline) Exec() {
msg := p.input.Receive()
msg = p.filter.Process(msg)
p.output.Send(msg)
}
// 启动的顺序 output -> filter -> input
func (p *Pipeline) Start() {
p.output.Start()
p.filter.Start()
p.input.Start()
p.status = plugin.Started
fmt.Println("Hello input plugin started.")
}
// 停止的顺序 input -> filter -> output
func (p *Pipeline) Stop() {
p.input.Stop()
p.filter.Stop()
p.output.Stop()
p.status = plugin.Stopped
fmt.Println("Hello input plugin stopped.")
}
func (p *Pipeline) Status() plugin.Status {
return p.status
}
ОдинPipeline
Зависит отInput
,Filter
,Output
Три типа подключаемых модулей образуют отношение «часть-целое», и все они реализуютPlugin
интерфейс, который является реализацией типичного шаблона композиции. Клиент не должен запускаться и останавливаться явноInput
,Filter
иOutput
плагин, вызовPipeline
объектStart
иStop
метод,Pipeline
Это уже помогло вам выполнить запуск и остановку соответствующих плагинов по порядку.
По сравнению с предыдущей статьей реализация в этой статьеInput
,Filter
,Output
При наличии трех типов подключаемых модулей необходимо реализовать еще три метода жизненного цикла. как в предыдущей статьеHelloInput
,UpperFilter
иConsoleOutput
Например, конкретная реализация выглядит следующим образом:
package plugin
...
type HelloInput struct {
status Status
}
func (h *HelloInput) Receive() *msg.Message {
// 如果插件未启动,则返回nil
if h.status != Started {
fmt.Println("Hello input plugin is not running, input nothing.")
return nil
}
return msg.Builder().
WithHeaderItem("content", "text").
WithBodyItem("Hello World").
Build()
}
func (h *HelloInput) Start() {
h.status = Started
fmt.Println("Hello input plugin started.")
}
func (h *HelloInput) Stop() {
h.status = Stopped
fmt.Println("Hello input plugin stopped.")
}
func (h *HelloInput) Status() Status {
return h.status
}
package plugin
...
type UpperFilter struct {
status Status
}
func (u *UpperFilter) Process(msg *msg.Message) *msg.Message {
if u.status != Started {
fmt.Println("Upper filter plugin is not running, filter nothing.")
return msg
}
for i, val := range msg.Body.Items {
msg.Body.Items[i] = strings.ToUpper(val)
}
return msg
}
func (u *UpperFilter) Start() {
u.status = Started
fmt.Println("Upper filter plugin started.")
}
func (u *UpperFilter) Stop() {
u.status = Stopped
fmt.Println("Upper filter plugin stopped.")
}
func (u *UpperFilter) Status() Status {
return u.status
}
package plugin
...
type ConsoleOutput struct {
status Status
}
func (c *ConsoleOutput) Send(msg *msg.Message) {
if c.status != Started {
fmt.Println("Console output is not running, output nothing.")
return
}
fmt.Printf("Output:\n\tHeader:%+v, Body:%+v\n", msg.Header.Items, msg.Body.Items)
}
func (c *ConsoleOutput) Start() {
c.status = Started
fmt.Println("Console output plugin started.")
}
func (c *ConsoleOutput) Stop() {
c.status = Stopped
fmt.Println("Console output plugin stopped.")
}
func (c *ConsoleOutput) Status() Status {
return c.status
}
Код теста выглядит следующим образом:
package test
...
func TestPipeline(t *testing.T) {
p := pipeline.Of(pipeline.DefaultConfig())
p.Start()
p.Exec()
p.Stop()
}
// 运行结果
=== RUN TestPipeline
Console output plugin started.
Upper filter plugin started.
Hello input plugin started.
Pipeline started.
Output:
Header:map[content:text], Body:[HELLO WORLD]
Hello input plugin stopped.
Upper filter plugin stopped.
Console output plugin stopped.
Hello input plugin stopped.
--- PASS: TestPipeline (0.00s)
PASS
Другая реализация шаблона композиции, Embedding Composition, на самом деле использует функцию анонимного члена языка Go, которая по существу аналогична прямой композиции.
По-прежнему беря в качестве примера структуру Message, если используется встроенная композиция, она выглядит так:
type Message struct {
Header
Body
}
// 使用时,Message可以引用Header和Body的成员属性,例如:
msg := &Message{}
msg.SrcAddr = "192.168.0.1"
Шаблон адаптера
Кратко
Шаблон адаптера является одним из наиболее часто используемых структурных шаблонов и позволяет двум объектам работать вместе, которые в противном случае не работали бы вместе из-за несогласованного интерфейса. В реальной жизни режимы адаптера можно увидеть повсюду, например преобразователи вилок питания, которые могут заставить вилки британского стиля работать с розетками китайского типа. Что делает шаблон адаптера?интерфейсAdaptee
, через переходникAdapter
Преобразование в другой интерфейс, ожидаемый КлиентомTarget
Использовать, принцип реализации тоже очень прост, т.е.Adapter
путем реализацииTarget
интерфейс и вызвать его в соответствующем методеAdaptee
реализация интерфейса.
Типичный сценарий применения:Старый интерфейс в системе устарел и будет удален.Однако из-за исторической нагрузки старый интерфейс не может быть немедленно заменен новым интерфейсом.В настоящее время можно добавить адаптер для адаптации старого интерфейса к новому интерфейсу. для использования.. Шаблон адаптера является хорошей практикой принципов объектно-ориентированного проектирования.принцип открыто-закрыто(открытый/закрытый принцип), при добавлении интерфейса не нужно модифицировать старый интерфейс, нужно только добавить дополнительный адаптационный слой.
Перейти к реализации
Продолжая рассматривать пример системы обработки сообщений из предыдущего раздела, до сих пор входные данные для системы были получены изHelloInput
, теперь предположим, что в систему необходимо добавить функцию получения данных из очереди сообщений Kafka, где интерфейс потребителя Kafka выглядит следующим образом:
package kafka
...
type Records struct {
Items []string
}
type Consumer interface {
Poll() Records
}
Из-за текущегоPipeline
Это разработаноplugin.Input
Интерфейс для приема данных, таким образом,kafka.Consumer
и не могут быть напрямую интегрированы в систему.
что делать? Используйте режим адаптера!
чтобы позволитьPipeline
может быть использованkafka.Consumer
интерфейс, нам нужно определить адаптер следующим образом:
package plugin
...
type KafkaInput struct {
status Status
consumer kafka.Consumer
}
func (k *KafkaInput) Receive() *msg.Message {
records := k.consumer.Poll()
if k.status != Started {
fmt.Println("Kafka input plugin is not running, input nothing.")
return nil
}
return msg.Builder().
WithHeaderItem("content", "kafka").
WithBodyItems(records.Items).
Build()
}
// 在输入插件映射关系中加入kafka,用于通过反射创建input对象
func init() {
inputNames["hello"] = reflect.TypeOf(HelloInput{})
inputNames["kafka"] = reflect.TypeOf(KafkaInput{})
}
...
Поскольку в языке Go нет конструктора, если следовать инструкциям из предыдущей статьиАбстрактный заводской узорсоздаватьKafkaInput
, то полученный экземплярconsumer
Член будет, потому что он не инициализированnil
. Поэтому необходимо датьPlugin
добавить новый интерфейсInit
Метод, который используется для определения некоторых операций инициализации плагина и называется до того, как завод возвращает экземпляр.
package plugin
...
type Plugin interface {
Start()
Stop()
Status() Status
// 新增初始化方法,在插件工厂返回实例前调用
Init()
}
// 修改后的插件工厂实现如下
func (i *InputFactory) Create(conf Config) Plugin {
t, _ := inputNames[conf.Name]
p := reflect.New(t).Interface().(Plugin)
// 返回插件实例前调用Init函数,完成相关初始化方法
p.Init()
return p
}
// KakkaInput的Init函数实现
func (k *KafkaInput) Init() {
k.consumer = &kafka.MockConsumer{}
}
в приведенном выше кодеkafka.MockConsumer
Для реализации нашего шаблона Kafka Consumer код выглядит следующим образом:
package kafka
...
type MockConsumer struct {}
func (m *MockConsumer) Poll() *Records {
records := &Records{}
records.Items = append(records.Items, "i am mock consumer.")
return records
}
Код теста выглядит следующим образом:
package test
...
func TestKafkaInputPipeline(t *testing.T) {
config := pipeline.Config{
Name: "pipeline2",
Input: plugin.Config{
PluginType: plugin.InputType,
Name: "kafka",
},
Filter: plugin.Config{
PluginType: plugin.FilterType,
Name: "upper",
},
Output: plugin.Config{
PluginType: plugin.OutputType,
Name: "console",
},
}
p := pipeline.Of(config)
p.Start()
p.Exec()
p.Stop()
}
// 运行结果
=== RUN TestKafkaInputPipeline
Console output plugin started.
Upper filter plugin started.
Kafka input plugin started.
Pipeline started.
Output:
Header:map[content:kafka], Body:[I AM MOCK CONSUMER.]
Kafka input plugin stopped.
Upper filter plugin stopped.
Console output plugin stopped.
Pipeline stopped.
--- PASS: TestKafkaInputPipeline (0.00s)
PASS
Шаблон моста
Кратко
Мостовой режим в основном используется дляРазделите абстрактную часть и часть реализации, чтобы они могли изменяться в независимых направлениях..它解决了在模块有多种变化方向的情况下,用继承所导致的类爆炸问题。举一个例子,一个产品有形状和颜色两个特征(变化方向),其中形状分为方形和圆形,颜色分为红色和蓝色。如果采用继承的设计方案,那么就需要新增4个产品子类:方形红色、圆形红色、方形蓝色、圆形红色。如果形状总共有m种变化,颜色有n种变化,那么就需要新增m*n个产品子类!现在我们使用桥接模式进行优化,将形状和颜色分别设计为一个抽象接口独立出来,这样需要新增2个形状子类:方形和圆形,以及2个颜色子类:红色和蓝色。同样,如果形状总共有m种变化,颜色有n种变化,总共只需要新增m+n个子类!
В приведенном выше примере, абстрагируя форму и цвет как интерфейс, продукт больше не зависит от конкретных деталей формы и цвета, чтобы достичь цели разделения.Режим моста — это, по сути, интерфейсно-ориентированное программирование, которое может обеспечить хорошую гибкость и масштабируемость системы.. Если для объекта существует несколько направлений изменений, и каждое направление изменений необходимо расширить, то для проектирования более целесообразно использовать шаблон моста.
Перейти к реализации
Возвращаясь к примеру системы обработки сообщений,Pipeline
Объект в основном состоит изInput
,Filter
,Output
Три типа плагинов (3 функции), поскольку это подключаемая система, поддержка различныхInput
,Filter
,Output
, и могут быть гибко объединены (Есть несколько направлений изменений). Очевидно,Pipeline
Это очень удобно для проектирования в режиме моста, и мы на самом деле тоже так делаем. мы будемInput
,Filter
,Output
Они разработаны как абстрактный интерфейс и расширяются в соответствующих направлениях.Pipeline
Он зависит только от этих трех абстрактных интерфейсов и не воспринимает детали конкретной реализации.
package plugin
...
type Input interface {
Plugin
Receive() *msg.Message
}
type Filter interface {
Plugin
Process(msg *msg.Message) *msg.Message
}
type Output interface {
Plugin
Send(msg *msg.Message)
}
package pipeline
...
// 一个Pipeline由input、filter、output三个Plugin组成
type Pipeline struct {
status plugin.Status
input plugin.Input
filter plugin.Filter
output plugin.Output
}
// 通过抽象接口来使用,看不到底层的实现细节
func (p *Pipeline) Exec() {
msg := p.input.Receive()
msg = p.filter.Process(msg)
p.output.Send(msg)
}
Код теста выглядит следующим образом:
package test
...
func TestPipeline(t *testing.T) {
p := pipeline.Of(pipeline.DefaultConfig())
p.Start()
p.Exec()
p.Stop()
}
// 运行结果
=== RUN TestPipeline
Console output plugin started.
Upper filter plugin started.
Hello input plugin started.
Pipeline started.
Output:
Header:map[content:text], Body:[HELLO WORLD]
Hello input plugin stopped.
Upper filter plugin stopped.
Console output plugin stopped.
Pipeline stopped.
--- PASS: TestPipeline (0.00s)
PASS
Суммировать
В этой статье в основном представлены режим композиции, режим адаптера и режим моста в структурном режиме.Комбинированный режимЭто в основном решает проблему повторного использования кода.По сравнению с отношениями наследования, комбинированный режим может избежать проблемы сложного кода, вызванного слишком глубоким наследованием.Поэтому область объектно-ориентированного дизайна популярна.Композиция над наследованиемПринцип и дизайн языка Go также являются хорошей практикой этого принципа;режим адаптераОн можно рассматривать как мост между двумя несовместимыми интерфейсами, которые могут преобразовать один интерфейс в другой интерфейс, ожидаемый клиентом, который решает проблему, которую модули не могут работать вместе из-за несовместимых интерфейсов;режим мостаАбстрактная часть и часть реализации модуля разделены, чтобы они могли расширяться в своем собственном направлении, чтобы достичь цели разделения.
В следующей статье мы продолжим фокусироваться на структурных паттернах и представим оставшиеся 4 паттерна узлов: декоративные паттерны, паттерны фасадов, паттерны легковеса и паттерны прокси.