предисловие
23 предложения от GoF в 1995 г.Шаблоны проектированияСейчас, 25 лет спустя, шаблоны проектирования по-прежнему остаются горячей темой в мире программного обеспечения. На данный момент, если вы не знаете немного шаблона проектирования, вам стыдно говорить, что вы квалифицированный программист. Шаблоны проектирования обычно определяются как:
Шаблон проектирования — это набор многократного использования, известный большинству людей, классифицированный и каталогизированный, а также сводка опыта разработки кода.Шаблоны проектирования используются для повторного использования кода, облегчения его понимания другими и обеспечения надежности кода.
По определению,Паттерн проектирования на самом деле является обобщением опыта, кратким и элегантным решением конкретной проблемы.. Поскольку это обобщение опыта, наиболее непосредственным преимуществом изучения шаблонов проектирования является то, что вы можете встать на плечи гигантов, чтобы решить некоторые конкретные проблемы в процессе разработки программного обеспечения. Однако высший уровень изучения шаблонов проектирования — это приобретение идей, используемых для решения проблем.Даже если вы забыли название и структуру шаблона проектирования, вы все равно можете использовать его для решения конкретной задачи..
Некоторые люди рекламируют хорошие вещи, и, конечно же, они также будут привлекать чернокожих. Шаблоны проектирования подвергались критике по двум основным причинам:
1,Паттерны проектирования увеличат объем кода и усложнят логику программы.. Это неизбежно, но мы не можем просто учитывать стоимость этапа разработки. Простейшая программа, конечно, представляет собой функцию, написанную от начала до конца, но стоимость обслуживания на более позднем этапе станет очень большой, и хотя шаблон проектирования немного увеличивает стоимость разработки, он позволяет людям писать повторно используемую и поддерживаемую программу. . Цитируя концепцию из «Философии дизайна программного обеспечения», перваятактическое программирование, последнийстратегическое программирование,мы должныСкажи нет тактическому программированию! (Пожалуйста, переместите«Шаг за шагом, чтобы уменьшить сложность программного обеспечения»)
2,Злоупотребление шаблонами проектирования. Это самая распространенная ошибка, которую совершают новички, изучая паттерн, они хотят использовать его во всем коде, чтобы паттерн сознательно использовался там, где паттерн использовать не следует, что делает программу чрезвычайно сложной. На самом деле каждый шаблон проектирования имеет несколько ключевых элементов:Применимая сцена,Решение,Преимущества и недостатки. Паттерны не панацея, они работают только с конкретными проблемами. Итак, прежде чем использовать шаблон, спросите себя, применим ли этот шаблон к текущей ситуации?
Подзаголовок книги «Шаблоны проектирования» — «Основы многоразового объектно-ориентированного программного обеспечения», но это не означает, что только объектно-ориентированные языки могут использовать шаблоны проектирования. Шаблон — это просто идея для решения конкретной проблемы, а не язык. Как и Go, это не объектно-ориентированный язык, как C++ и Java, но к нему применимы шаблоны проектирования. В этой серии статей язык Go будет использоваться для реализации 23 шаблонов проектирования, предложенных GoF.творческий режим(творческий шаблон),Структурный образец(Структурный образец) иповеденческая модель(Поведенческий паттерн) разделен на три категории, и текст в основном знакомит с творческими паттернами.
Синглтон шаблон
Кратко
Одноэлементный шаблон — самый простой из 23 шаблонов проектирования.Гарантирует наличие только одного экземпляра класса и предоставляет к нему глобальную точку доступа.
В программировании есть некоторые объекты, для которых нам обычно нужен только общий экземпляр, например пул потоков, глобальный кеш, пул объектов и т. д. В этом сценарии подходит шаблон singleton.
Однако не все глобально уникальные сценарии подходят для использования одноэлементного шаблона. Например, рассмотрим ситуацию, когда вам нужно подсчитать вызов API, есть два показателя, количество успешных вызовов и количество неудачных вызовов. Обе метрики глобально уникальны, поэтому кто-то может смоделировать их как два синглтона.SuccessApiMetric
иFailApiMetric
. Согласно этой идее, по мере увеличения количества индикаторов вы обнаружите, что в коде будет все больше и больше определений классов, и он будет становиться все более и более раздутым. Это также наиболее распространенный сценарий неправильного использования одноэлементного шаблона, лучший способ — спроектировать два индикатора в один объект.ApiMetric
Два примера нижеApiMetic success
иApiMetic fail
.
Как определить, следует ли моделировать объект как синглтон?
Как правило, объекты, моделируемые как синглтоны, имеют "Центральная точка«Смысл», например, в том, что пул потоков — это центр управления всеми потоками. Итак, судя, подходит ли объект для паттерна singleton, сначала подумайте, является ли этот объект центральной точкой?
Перейти к реализации
При реализации одноэлементного шаблона для объекта необходимо обратить внимание на два момента: (1)ограничить вызывающую сторону от прямого создания экземпляра объекта;(2)Предоставляет глобально уникальный метод доступа к синглтону объекта..
Для C++/Java просто создайте конструктор класса как закрытый и предоставьтеstatic
метод для доступа к единственному экземпляру точки класса. Но для языка Go нет ни понятия конструктора, ниstatic
метод, поэтому нам нужно найти другой выход.
Мы можем использовать язык Gopackage
Разрабатывая одноэлементную структуру так, чтобы первая буква в нижнем регистре, она может ограничить область доступа только текущим пакетом, имитируя закрытый конструктор в C++/Java;package
Реализация функции доступа с заглавной буквы эквивалентнаstatic
метод работает.
В реальной разработке мы часто сталкиваемся с объектами, которые необходимо часто создавать и уничтожать. Частое создание и уничтожение потребляет процессор, а использование памяти невелико.Обычно мы используем технологию пула объектов для оптимизации. Учтите, что нам нужно реализовать пул объектов сообщений. Поскольку он является глобальной центральной точкой и управляет всеми экземплярами сообщений, он реализован как синглтон. Код реализации выглядит следующим образом:
package msgpool
...
// 消息池
type messagePool struct {
pool *sync.Pool
}
// 消息池单例
var msgPool = &messagePool{
// 如果消息池里没有消息,则新建一个Count值为0的Message实例
pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }},
}
// 访问消息池单例的唯一方法
func Instance() *messagePool {
return msgPool
}
// 往消息池里添加消息
func (m *messagePool) AddMsg(msg *Message) {
m.pool.Put(msg)
}
// 从消息池里获取消息
func (m *messagePool) GetMsg() *Message {
return m.pool.Get().(*Message)
}
...
Код теста выглядит следующим образом:
package test
...
func TestMessagePool(t *testing.T) {
msg0 := msgpool.Instance().GetMsg()
if msg0.Count != 0 {
t.Errorf("expect msg count %d, but actual %d.", 0, msg0.Count)
}
msg0.Count = 1
msgpool.Instance().AddMsg(msg0)
msg1 := msgpool.Instance().GetMsg()
if msg1.Count != 1 {
t.Errorf("expect msg count %d, but actual %d.", 1, msg1.Count)
}
}
// 运行结果
=== RUN TestMessagePool
--- PASS: TestMessagePool (0.00s)
PASS
Приведенный выше одноэлементный шаблон является типичным "режим голодного человека", экземпляр был инициализирован при загрузке системы. Соответственно, есть и "Ленивый режим", только когда объект используется, он будет инициализирован, что в определенной степени экономит память. Как мы все знаем, "ленивый режим" принесет проблемы с безопасностью потоков, которые можно решить с помощьюнормальный замок, или более эффективныйзамок с двойной проверкойоптимизировать. Для «ленивого режима» язык Go имеет более элегантную реализацию, которая заключается в использованииsync.Once
, который имеетDo
метод, входным параметром которого является метод, а язык Go гарантирует, что метод вызывается только один раз.
// 单例模式的“懒汉模式”实现
package msgpool
...
var once = &sync.Once{}
// 消息池单例,在首次调用时初始化
var msgPool *messagePool
// 全局唯一获取消息池pool到方法
func Instance() *messagePool {
// 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次
once.Do(func() {
msgPool = &messagePool{
// 如果消息池里没有消息,则新建一个Count值为0的Message实例
pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }},
}
})
return msgPool
}
...
Шаблон строителя
Кратко
В программировании мы часто сталкиваемся с некоторыми сложными объектами, которые имеют много свойств-членов и даже вкладывают несколько сложных объектов. В этом случае создание этого сложного объекта становится утомительным. Для C++/Java наиболее распространенным проявлением является наличие у конструкторов длинных списков параметров:
MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)
Для языка Go наиболее распространенным проявлением является многоуровневое вложенное создание экземпляров:
obj := &MyObject{
Field1: &Field1 {
Param1: &Param1 {
Val: 0,
},
Param2: &Param2 {
Val: 1,
},
...
},
Field2: &Field2 {
Param3: &Param3 {
Val: 2,
},
...
},
...
}
Описанный выше метод создания объекта имеет два очевидных недостатка: (1)Недружественный к пользователям объекта, пользователь должен знать слишком много деталей при создании объекта; (2)Читабельность кода плохая.
Для таких сценариев с большим количеством объектов-членов и громоздкой логикой создания объектов целесообразно использовать для оптимизации режим построителя.
Роль шаблона построителя заключается в следующем:
1. Инкапсулировать процесс создания сложных объектов, чтобы пользователи объекта не воспринимали сложную логику создания.
2. Вы можете назначать значения членам последовательно шаг за шагом или создавать вложенные объекты и, наконец, завершить создание целевого объекта.
3. Повторно используйте одну и ту же логику создания объектов для нескольких объектов.
Среди них чаще используются точки 1 и 2, и реализация шаблона построителя ниже в основном для этих двух точек.
Перейти к реализации
Рассмотрим один из следующихMessage
структура, состоящая в основном изHeader
иBody
сочинение:
package msg
...
type Message struct {
Header *Header
Body *Body
}
type Header struct {
SrcAddr string
SrcPort uint64
DestAddr string
DestPort uint64
Items map[string]string
}
type Body struct {
Items []string
}
...
Если вы следуете методу прямого создания объекта, логика создания должна быть такой:
// 多层的嵌套实例化
message := msg.Message{
Header: &msg.Header{
SrcAddr: "192.168.0.1",
SrcPort: 1234,
DestAddr: "192.168.0.2",
DestPort: 8080,
Items: make(map[string]string),
},
Body: &msg.Body{
Items: make([]string, 0),
},
}
// 需要知道对象的实现细节
message.Header.Items["contents"] = "application/json"
message.Body.Items = append(message.Body.Items, "record1")
message.Body.Items = append(message.Body.Items, "record2")
Несмотря на то чтоMessage
Уровней вложенности структуры не много, но судя по коду ее создания она естьНедружественный к пользователям объектаиПлохая читаемость кодаНедостатки. Ниже мы представляем шаблон построителя для рефакторинга кода:
package msg
...
// Message对象的Builder对象
type builder struct {
once *sync.Once
msg *Message
}
// 返回Builder对象
func Builder() *builder {
return &builder{
once: &sync.Once{},
msg: &Message{Header: &Header{}, Body: &Body{}},
}
}
// 以下是对Message成员对构建方法
func (b *builder) WithSrcAddr(srcAddr string) *builder {
b.msg.Header.SrcAddr = srcAddr
return b
}
func (b *builder) WithSrcPort(srcPort uint64) *builder {
b.msg.Header.SrcPort = srcPort
return b
}
func (b *builder) WithDestAddr(destAddr string) *builder {
b.msg.Header.DestAddr = destAddr
return b
}
func (b *builder) WithDestPort(destPort uint64) *builder {
b.msg.Header.DestPort = destPort
return b
}
func (b *builder) WithHeaderItem(key, value string) *builder {
// 保证map只初始化一次
b.once.Do(func() {
b.msg.Header.Items = make(map[string]string)
})
b.msg.Header.Items[key] = value
return b
}
func (b *builder) WithBodyItem(record string) *builder {
b.msg.Body.Items = append(b.msg.Body.Items, record)
return b
}
// 创建Message对象,在最后一步调用
func (b *builder) Build() *Message {
return b.msg
}
Код теста выглядит следующим образом:
package test
...
func TestMessageBuilder(t *testing.T) {
// 使用消息建造者进行对象创建
message := msg.Builder().
WithSrcAddr("192.168.0.1").
WithSrcPort(1234).
WithDestAddr("192.168.0.2").
WithDestPort(8080).
WithHeaderItem("contents", "application/json").
WithBodyItem("record1").
WithBodyItem("record2").
Build()
if message.Header.SrcAddr != "192.168.0.1" {
t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr)
}
if message.Body.Items[0] != "record1" {
t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0])
}
}
// 运行结果
=== RUN TestMessageBuilder
--- PASS: TestMessageBuilder (0.00s)
PASS
Как видно из тестового кода, при использовании шаблона построителя для создания объектов пользователям больше не нужно знать конкретные детали реализации объекта, а читабельность кода также улучшается.
Шаблон фабричного метода
Кратко
Шаблон фабричного метода аналогичен шаблону построителя, рассмотренному в предыдущем разделе.Инкапсулируйте логику создания объектов и предоставьте пользователям простой в использовании интерфейс создания объектов.. У них несколько разные сценарии приложений, а шаблон построителя чаще используется в сценариях, где для создания экземпляра необходимо передать несколько параметров.
Есть два основных преимущества использования фабричных методов для создания объектов:
1,Код стал более читаемым. По сравнению с использованием конструкторов в C++/Java или в Go{}
Для создания объекта фабричный метод лучше читается, поскольку он может выражать смысл кода через имя функции. Например, используя фабричный методproductA := CreateProductA()
СоздаватьProductA
объекта, чем непосредственно с помощьюproductA := ProductA{}
лучшая читаемость.
2,Отделение от пользовательского кода. Во многих случаях создание объектов часто является моментом, который легко изменить.Инкапсуляция процесса создания объектов с помощью фабричных методов позволяет избежать логических изменений.Модификация дробовика.
Существует также два способа реализации шаблона фабричного метода: (1) предоставить фабричный объект и создать объект продукта, вызвав фабричный метод фабричного объекта; (2) интегрировать фабричный метод в объект продукта (объект в C++). /Ява).static
метод, то же самое в Gopackage
функция ниже)
Перейти к реализации
Подумайте о том, чтобы иметь объект событияEvent
, соответственно, есть два допустимых типа времениStart
иEnd
:
package event
...
type Type uint8
// 事件类型定义
const (
Start Type = iota
End
)
// 事件抽象接口
type Event interface {
EventType() Type
Content() string
}
// 开始事件,实现了Event接口
type StartEvent struct{
content string
}
...
// 结束事件,实现了Event接口
type EndEvent struct{
content string
}
...
1. По первому способу реализацииEvent
Предоставьте фабричный объект, конкретный код выглядит следующим образом:
package event
...
// 事件工厂对象
type Factory struct{}
// 更具事件类型创建具体事件
func (e *Factory) Create(etype Type) Event {
switch etype {
case Start:
return &StartEvent{
content: "this is start event",
}
case End:
return &EndEvent{
content: "this is end event",
}
default:
return nil
}
}
Код теста выглядит следующим образом:
package test
...
func TestEventFactory(t *testing.T) {
factory := event.Factory{}
e := factory.Create(event.Start)
if e.EventType() != event.Start {
t.Errorf("expect event.Start, but actual %v.", e.EventType())
}
e = factory.Create(event.End)
if e.EventType() != event.End {
t.Errorf("expect event.End, but actual %v.", e.EventType())
}
}
// 运行结果
=== RUN TestEventFactory
--- PASS: TestEventFactory (0.00s)
PASS
2. По второму способу реализации даемStart
иEnd
ТипEvent
Предоставьте отдельный фабричный метод, код выглядит следующим образом:
package event
...
// Start类型Event的工厂方法
func OfStart() Event {
return &StartEvent{
content: "this is start event",
}
}
// End类型Event的工厂方法
func OfEnd() Event {
return &EndEvent{
content: "this is end event",
}
}
Код теста выглядит следующим образом:
package event
...
func TestEvent(t *testing.T) {
e := event.OfStart()
if e.EventType() != event.Start {
t.Errorf("expect event.Start, but actual %v.", e.EventType())
}
e = event.OfEnd()
if e.EventType() != event.End {
t.Errorf("expect event.End, but actual %v.", e.EventType())
}
}
// 运行结果
=== RUN TestEvent
--- PASS: TestEvent (0.00s)
PASS
Абстрактный заводской узор
Кратко
В шаблоне фабричного метода мы создаем семейство продуктов через фабричный объект.swtich-case
способ судить. Это также означает, что для каждого нового типа объекта продукта в группе продуктов код исходного объекта фабрики должен быть изменен; и по мере того, как количество продуктов продолжает увеличиваться, обязанности объекта фабрики становятся все тяжелее и тяжелее, что нарушаетПринцип единой ответственности.
Шаблон абстрактной фабрики решает эту проблему, добавляя уровень абстракции к классу фабрики, как показано на рисунке выше.FactoryA
иFactoryB
Оба реализуют интерфейс абстрактной фабрики, который используется для созданияProductA
иProductB
. Если последующие дополненияProductC
, просто добавьтеFactoryC
Вот и все, не нужно изменять исходный код, поскольку каждая фабрика отвечает только за создание одного продукта, она также следуетПринцип единой ответственности.
Перейти к реализации
Рассмотрим систему обработки сообщений, для которой требуется следующий стиль архитектуры плагинов:pipeline
это конвейер обработки сообщений, который содержитinput
,filter
иoutput
Три плагина. Нам нужно реализовать в соответствии с конфигурацией для созданияpipeline
, реализация процесса загрузки плагина хорошо подходит для использования фабричного шаблона, гдеinput
,filter
иoutput
Создание трех типов плагинов использует абстрактный заводской шаблон, в то время какpipeline
создается с использованием шаблона фабричного метода.
различные плагины иpipeline
Интерфейс определяется следующим образом:
package plugin
...
// 插件抽象接口定义
type Plugin interface {}
// 输入插件,用于接收消息
type Input interface {
Plugin
Receive() string
}
// 过滤插件,用于处理消息
type Filter interface {
Plugin
Process(msg string) string
}
// 输出插件,用于发送消息
type Output interface {
Plugin
Send(msg string)
}
package pipeline
...
// 消息管道的定义
type Pipeline struct {
input plugin.Input
filter plugin.Filter
output plugin.Output
}
// 一个消息的处理流程为 input -> filter -> output
func (p *Pipeline) Exec() {
msg := p.input.Receive()
msg = p.filter.Process(msg)
p.output.Send(msg)
}
Далее мы определяемinput
,filter
,output
Конкретная реализация трех типов подключаемых интерфейсов:
package plugin
...
// input插件名称与类型的映射关系,主要用于通过反射创建input对象
var inputNames = make(map[string]reflect.Type)
// Hello input插件,接收“Hello World”消息
type HelloInput struct {}
func (h *HelloInput) Receive() string {
return "Hello World"
}
// 初始化input插件映射关系表
func init() {
inputNames["hello"] = reflect.TypeOf(HelloInput{})
}
package plugin
...
// filter插件名称与类型的映射关系,主要用于通过反射创建filter对象
var filterNames = make(map[string]reflect.Type)
// Upper filter插件,将消息全部字母转成大写
type UpperFilter struct {}
func (u *UpperFilter) Process(msg string) string {
return strings.ToUpper(msg)
}
// 初始化filter插件映射关系表
func init() {
filterNames["upper"] = reflect.TypeOf(UpperFilter{})
}
package plugin
...
// output插件名称与类型的映射关系,主要用于通过反射创建output对象
var outputNames = make(map[string]reflect.Type)
// Console output插件,将消息输出到控制台上
type ConsoleOutput struct {}
func (c *ConsoleOutput) Send(msg string) {
fmt.Println(msg)
}
// 初始化output插件映射关系表
func init() {
outputNames["console"] = reflect.TypeOf(ConsoleOutput{})
}
Затем мы определяем интерфейс абстрактной фабрики плагина и реализацию фабрики соответствующего плагина:
package plugin
...
// 插件抽象工厂接口
type Factory interface {
Create(conf Config) Plugin
}
// input插件工厂对象,实现Factory接口
type InputFactory struct{}
// 读取配置,通过反射机制进行对象实例化
func (i *InputFactory) Create(conf Config) Plugin {
t, _ := inputNames[conf.Name]
return reflect.New(t).Interface().(Plugin)
}
// filter和output插件工厂实现类似
type FilterFactory struct{}
func (f *FilterFactory) Create(conf Config) Plugin {
t, _ := filterNames[conf.Name]
return reflect.New(t).Interface().(Plugin)
}
type OutputFactory struct{}
func (o *OutputFactory) Create(conf Config) Plugin {
t, _ := outputNames[conf.Name]
return reflect.New(t).Interface().(Plugin)
}
окончательное определениеpipeline
Заводской метод вызоваplugin.Factory
Абстрактная фабрика завершает создание объекта конвейера:
package pipeline
...
// 保存用于创建Plugin的工厂实例,其中map的key为插件类型,value为抽象工厂接口
var pluginFactories = make(map[plugin.Type]plugin.Factory)
// 根据plugin.Type返回对应Plugin类型的工厂实例
func factoryOf(t plugin.Type) plugin.Factory {
factory, _ := pluginFactories[t]
return factory
}
// pipeline工厂方法,根据配置创建一个Pipeline实例
func Of(conf Config) *Pipeline {
p := &Pipeline{}
p.input = factoryOf(plugin.InputType).Create(conf.Input).(plugin.Input)
p.filter = factoryOf(plugin.FilterType).Create(conf.Filter).(plugin.Filter)
p.output = factoryOf(plugin.OutputType).Create(conf.Output).(plugin.Output)
return p
}
// 初始化插件工厂对象
func init() {
pluginFactories[plugin.InputType] = &plugin.InputFactory{}
pluginFactories[plugin.FilterType] = &plugin.FilterFactory{}
pluginFactories[plugin.OutputType] = &plugin.OutputFactory{}
}
Код теста выглядит следующим образом:
package test
...
func TestPipeline(t *testing.T) {
// 其中pipeline.DefaultConfig()的配置内容见【抽象工厂模式示例图】
// 消息处理流程为 HelloInput -> UpperFilter -> ConsoleOutput
p := pipeline.Of(pipeline.DefaultConfig())
p.Exec()
}
// 运行结果
=== RUN TestPipeline
HELLO WORLD
--- PASS: TestPipeline (0.00s)
PASS
Образец прототипа
Кратко
Режим прототипа в основном решает проблему репликации объекта, и его ядром являетсяclone()
метод, возвращаетPrototype
Реплика объекта. В процессе программирования часто возникают сценарии, требующие большого количества одинаковых объектов, и если шаблон прототипа не используется, то мы можем создавать такие объекты:Создайте новый экземпляр того же объекта, затем выполните итерацию по всем переменным-членам исходного объекта и скопируйте значения переменных-членов в новый объект.. Недостаток этого метода очевиден, то есть пользователь должен знать детали реализации объекта, в результате чего возникает связь между кодами. Кроме того, у объекта, вероятно, есть невидимые переменные, кроме самого объекта, и в этом случае этот метод не будет работать.
В этом случае лучше использовать шаблон прототипа, делегируя логику копирования самому объекту, чтобы решить обе вышеупомянутые проблемы.
Перейти к реализации
Или в разделе Builder ModeMessage
Например, теперь спроектируйтеPrototype
Абстрактный интерфейс:
package prototype
...
// 原型复制抽象接口
type Prototype interface {
clone() Prototype
}
type Message struct {
Header *Header
Body *Body
}
func (m *Message) clone() Prototype {
msg := *m
return &msg
}
Код теста выглядит следующим образом:
package test
...
func TestPrototype(t *testing.T) {
message := msg.Builder().
WithSrcAddr("192.168.0.1").
WithSrcPort(1234).
WithDestAddr("192.168.0.2").
WithDestPort(8080).
WithHeaderItem("contents", "application/json").
WithBodyItem("record1").
WithBodyItem("record2").
Build()
// 复制一份消息
newMessage := message.Clone().(*msg.Message)
if newMessage.Header.SrcAddr != message.Header.SrcAddr {
t.Errorf("Clone Message failed.")
}
if newMessage.Body.Items[0] != message.Body.Items[0] {
t.Errorf("Clone Message failed.")
}
}
// 运行结果
=== RUN TestPrototype
--- PASS: TestPrototype (0.00s)
PASS
Суммировать
В этой статье в основном представлены шаблоны создания 5 среди шаблонов проектирования 23 GoF.Цель шаблонов создания состоит в том, чтобыОбеспечьте простой интерфейс, чтобы отделить процесс создания объекта от пользователя.. в,одноэлементный шаблонВ основном используется для обеспечения того, чтобы у класса был только один экземпляр, и для предоставления глобальной точки доступа для доступа к нему;режим строителяВ основном он решает сценарии, в которых необходимо передать несколько параметров, когда необходимо создать объект или требуется последовательность инициализации;Шаблон фабричного методаПредоставляя фабричный объект или фабричный метод, детали создания объекта скрыты от пользователя;Абстрактный заводской узорЭто оптимизация шаблона фабричного метода.Добавляя уровень абстракции к фабричному объекту, фабричный объект следует принципу единой ответственности и избегает модификаций дробовика;режим прототипаЭто упрощает копирование объектов.
В следующей статье мы представим 7 из 23 шаблонов проектирования.Структурный образец(Структурный паттерн) и его реализация в Go.