Шаблоны проектирования программного обеспечения — это набор классифицированных и каталогизированных сводок опыта проектирования кода, которые используются неоднократно и известны большинству людей. Его значение состоит в том, что эти шаблоны представляют собой ценный опыт, обобщенный многими программистами после длительного периода проб и ошибок, который может помочь нам улучшить возможность повторного использования, читабельность и надежность кода. Эта статья была написана Ван Шуньчи, инженером по бизнес-безопасности в Tencent Cloud Tianyu. В ней обобщаются характеристики, преимущества, недостатки и сценарии применения 10 классических шаблонов проектирования программного обеспечения, а также приводятся соответствующие примеры кода.
Следите за разработчиками Tencent Cloud и заранее получайте техническую информацию из первых рук👇
Шаблон синглтон — это творческий шаблон проектирования, который ограничивает количество объектов, экземпляры которых могут быть созданы, гарантирует, что при любых обстоятельствах существует только один экземпляр класса, и предоставляет глобальную точку доступа. Этот шаблон полезен, когда требуется глобальный контроль состояния или доступ к общим ресурсам.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите структуру Singleton для хранения данных экземпляра синглтона.
type singleton struct {
value string // Здесь могут храниться любые данные одноэлементного объекта.
}
// Определите экземпляр глобальной переменной для хранения экземпляра синглтона.
var instance *singleton
// Функция getInstance используется для получения экземпляра синглтона.
// Если экземпляр равен нулю, создайте новый экземпляр Singleton.
// В противном случае верните существующий экземпляр
func getInstance() *singleton {
if instance == nil {
instance = &singleton{value: "unique instance"} // Инициализируйте экземпляр синглтона здесь
}
return instance // Вернуть экземпляр синглтона
}
func main() {
// Получить экземпляр синглтона
singletonInstance := getInstance()
fmt.Println(singletonInstance.value) // Выход: unique instance
// снова Получить экземпляр синглтона,вернется то же самоеиз Пример
anotherInstance := getInstance()
if singletonInstance == anotherInstance {
fmt.Println("Both instances are the same") // Выход: Both instances are the same
}
}
В параллельной среде, если нет соответствующего механизма синхронизации, несколько goroutine могут быть обнаружены одновременноinstance
дляnil
и попробуйсоздаватьновыйиз Пример,что приводит ксоздаватьнесколько Пример。для Чтобы решить эту проблему,Можно использоватьsync.Once
,Это гарантирует, что в параллельной среде выполняется только одна операция инициализации.
// Определите структуру Singleton для хранения данных экземпляра синглтона.
type singleton struct {
value string // Здесь могут храниться любые данные одноэлементного объекта.
}
// Определите объект Once, чтобы гарантировать, что операция инициализации выполняется только один раз.
var once sync.Once
// Определите экземпляр глобальной переменной для хранения экземпляра синглтона.
var instance *singleton
// Функция инициализации, вызываемая Once.Do
func initSingleton() {
instance = &singleton{value: "unique instance"} // Инициализируйте экземпляр синглтона здесь
}
// Функция getInstance используется для получения экземпляра синглтона.
func getInstance() *singleton {
// Выполните initSingleton, чтобы гарантировать, что экземпляр инициализируется только один раз.
once.Do(initSingleton)
return instance // Вернуть экземпляр синглтона
}
func main() {
// Получить экземпляр синглтона
singletonInstance := getInstance()
fmt.Println(singletonInstance.value) // Выход: unique instance
// снова Получить экземпляр синглтона,вернется то же самоеиз Пример
anotherInstance := getInstance()
if singletonInstance == anotherInstance {
fmt.Println("Both instances are the same") // Выход: Both instances are the same
}
// Тестирование одноэлементного режима в параллельной среде
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
singletonInstance := getInstance()
fmt.Println(singletonInstance.value)
}()
}
wg.Wait()
}
Шаблон «Фабрика» — это шаблон творческого проектирования, который инкапсулирует процесс создания объектов, а подкласс решает, экземпляр какого класса создавать. Этот шаблон делает структуру кода более понятной и позволяет легко заменять или расширять классы продуктов.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите интерфейсProduct, который объявляет все операции, которые должен реализовать конкретный объект продукта.
type Product interface {
operation() // Эксплуатация продукта объект
}
// Определите конкретный продукт ConcreteProductA и реализуйте интерфейс Productinterface.
type ConcreteProductA struct{}
func (p *ConcreteProductA) operation() {
fmt.Println("Operation of ConcreteProductA")
}
// Определите другой конкретный продукт ConcreteProductB, который также реализует интерфейс Productinterface.
type ConcreteProductB struct{}
func (p *ConcreteProductB) operation() {
fmt.Println("Operation of ConcreteProductB")
}
// Определите абстрактную фабрику Creator, которая объявляет фабричный метод FactoryMethod, который используется для создания объекта продуктов.
type Creator interface {
factoryMethod() Product // Фабричный метод создания объекта продукции
}
// Определите конкретную фабрику CreatorA, которая реализует интерфейс Creator.
type CreatorA struct{}
func (c *CreatorA) factoryMethod() Product {
return &ConcreteProductA{} // Бетонная фабрика CreatorA возвращает экземпляр ConcreteProductA.
}
// Определите еще одну конкретную фабрику CreatorB, которая также реализует интерфейс Creator.
type CreatorB struct{}
func (c *CreatorB) factoryMethod() Product {
return &ConcreteProductB{} // Бетонная фабрика CreatorB возвращает экземпляр ConcreteProductB.
}
func main() {
// Создайте экземпляр конкретной фабрики CreatorA.
creatorA := &CreatorA{}
productA := creatorA.factoryMethod()
productA.operation() // Вызов операции продукта А
// Создайте экземпляр конкретной фабрики CreatorB.
creatorB := &CreatorB{}
productB := creatorB.factoryMethod()
productB.operation() // Назовите операцию продукта B
}
Шаблон Observer — это шаблон поведенческого проектирования, который определяет отношение зависимости между объектами «один-ко-многим», поэтому при изменении состояния объекта все зависящие от него объекты будут уведомлены и автоматически обновлены. Этот шаблон очень подходит для реализации распределенных систем обработки событий.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите интерфейс Observer, который объявляет метод Update, который наблюдатель должен реализовать.
type Observer interface {
Update(string) // Этот метод будет вызываться при изменении статуса темы.
}
// Определите структуру субъекта, которая содержит список наблюдателей и методы для добавления или уведомления наблюдателей.
type Subject struct {
observers []Observer // Хранить список наблюдателей
}
// Метод Attach используется для добавления наблюдателя в список наблюдателей.
func (s *Subject) Attach(observer Observer) {
s.observers = append(s.observers, observer)
}
// Метод Notify используется для уведомления всех наблюдателей об изменениях состояния темы.
func (s *Subject) Notify(message string) {
for _, observer := range s.observers {
observer.Update(message) // Вызовите метод Update каждого наблюдателя.
}
}
// Определите конкретного наблюдателя ConcreteObserver, который реализует интерфейс Observer.
type ConcreteObserverA struct {
name string
}
// Реализуйте метод Update интерфейса Observer.
func (c *ConcreteObserverA) Update(message string) {
fmt.Printf("%s received message: %s\n", c.name, message)
}
func main() {
// Создать объект темы
subject := &Subject{}
// Создайте конкретный объект-наблюдатель
observerA := &ConcreteObserverA{name: "Observer A"}
// Добавить наблюдателя в список наблюдателей темы
subject.Attach(observerA)
// Уведомлять всех наблюдателей об изменении состояния темы.
subject.Notify("State changed to State 1")
}
Шаблон декоратора — это шаблон структурного проектирования, который позволяет пользователям динамически добавлять к объекту дополнительные обязанности или функции путем добавления объектов-декораторов без изменения самого объекта.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите Componentinterface, который является базовым классом для всех компонентов и декораторов.
type Component interface {
operation() // Операция, выполняемая компонентом
}
// Определите конкретный компонент ConcreteComponent и реализуйте Componentinterface.
type ConcreteComponent struct{}
func (c *ConcreteComponent) operation() {
fmt.Println("ConcreteComponent: performing basic operation")
}
// Определите абстрактную структуру Decorator, которая содержит поле типа Componentinterface.
type Decorator struct {
component Component // Используется для объединения компонентов интерфейса
}
// Реализуйте метод работы Декоратора и вызовите метод работы его Компонента.
func (d *Decorator) operation() {
if d.component != nil {
d.component.operation() // Вызов декорированной операции
}
}
// Определите конкретный декоратор ConcreteDecoratorA, который встраивает структуру Decorator.
type ConcreteDecoratorA struct {
Decorator // Наследовать Decorator для реализации функций оформления.
}
// Реализуйте метод операции для ConcreteDecoratorA и добавьте дополнительные обязанности.
func (cda *ConcreteDecoratorA) operation() {
cda.Decorator.operation() // Прежде всего декорированной операции
fmt.Println("ConcreteDecoratorA: added additional responsibilities")
}
func main() {
// Создание конкретных компонентов
component := &ConcreteComponent{}
// Создавайте декораторы и связывайте определенные компоненты
decoratorA := &ConcreteDecoratorA{Decorator{component}}
// Выполнение операций с декорированными компонентами
decoratorA.operation()
}
Шаблон стратегии — это шаблон поведенческого проектирования, который определяет ряд алгоритмов и инкапсулирует каждый алгоритм так, чтобы их можно было заменять друг другом. Паттерн «Стратегия» делает алгоритм независимым от использующего его клиента.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите интерфейс Strategy, который объявляет методы алгоритма, которые должны реализовать все конкретные стратегии.
type Strategy interface {
algorithm() // алгоритмический подход к стратегии
}
// Определите конкретную стратегию ConcreteStrategyA и реализуйте Strategyinterface.
type ConcreteStrategyA struct{}
func (c *ConcreteStrategyA) algorithm() {
fmt.Println("Executing Algorithm A")
}
// Определите еще одну конкретную стратегию ConcreteStrategyB, которая также реализует интерфейс Strategy.
type ConcreteStrategyB struct{}
func (c *ConcreteStrategyB) algorithm() {
fmt.Println("Executing Algorithm B")
}
// Определите структуру Context, которая содержит поле типа Strategyinterface.
type Context struct {
strategy Strategy // Используется для хранения текущей используемой политики.
}
// Метод выполнения стратегии, вызов метода алгоритма через поле стратегии в контексте.
func (c *Context) executeStrategy() {
c.strategy.algorithm() // Алгоритм реализации текущей стратегии
}
func main() {
// CreateContextобъект
context := &Context{}
// Создать конкретную стратегию объекта
strategyA := &ConcreteStrategyA{}
strategyB := &ConcreteStrategyB{}
// Установите политику контекста на политику A.
context.strategy = strategyA
context.executeStrategy() // Выход: Executing Algorithm A
// Изменить стратегию на стратегию B
context.strategy = strategyB
context.executeStrategy() // Выход: Executing Algorithm B
}
Шаблон «Адаптер» — это структурный шаблон проектирования, используемый для обеспечения совместной работы несовместимых в противном случае интерфейсов. Обычно при этом один клиент использует определенный ожидаемый интерфейс, а другой класс или компонент предоставляет другой интерфейс. Шаблон «Адаптер» преобразует интерфейс класса в другой интерфейс, ожидаемый клиентом, путем создания промежуточного уровня (адаптера).
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определение Целевой интерфейс представляет собой конкретный интерфейс, связанный с доменом, используемый клиентом.
type Target interface {
request() // Метод, который клиент ожидает вызвать
}
// Определите существующий класс Adaptee, который имеет собственный интерфейс.
type Adaptee struct{}
func (a *Adaptee) specificRequest() {
fmt.Println("Adaptee performs a specific request")
}
// Определите структуру адаптера, которая служит мостом между классами Target и Adaptee.
type Adapter struct {
adaptee *Adaptee // Цитата Adapteeобъект
}
// Адаптер реализует метод запроса целевого интерфейса.
// Этот метод внутренне делегирует метод specificRequest Adaptee.
func (a *Adapter) request() {
if a.adaptee != nil {
a.adaptee.specificRequest() // Делегированный метод вызова Adaptee
}
}
func main() {
// Создать объект Adaptee
adaptee := &Adaptee{}
// Создайте объект Adaptee и внедрите объект Adaptee.
adapter := &Adapter{adaptee: adaptee}
// Клиент использует Target, который реализуется через адаптер.
var target Target = adapter
target.request() // Метод вызова Adaptee через адаптер
}
Шаблон прокси — это структурный шаблон проектирования, который предоставляет замену или заполнитель для другого объекта для управления доступом к нему. Прокси-сервер может получить косвенный доступ к исходному объекту, вводя прокси-объект без изменения кода объекта, тем самым обеспечивая дополнительные функциональные операции без прямого раскрытия исходного объекта.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определите интерфейс субъекта, который объявляет истинную тему и актерское мастерство. Тема мастерства, разделяемая интерфейсом.
type Subject interface {
request() // Объявите метод запроса, настоящую тему и актёрское мастерство. Все темы мастерства будут реализовывать этот метод.
}
// RealSubject Структура реализована Subject интерфейс, представляет собой истинную тему.
type RealSubject struct{}
// RealSubject из request Метод реализован Subject интерфейсиз request метод,используется для выполнения фактическогоиздействовать。
func (r *RealSubject) request() {
fmt.Println("Real Subject") // Распечатать "Real Subject" Указывает, что вызывается реальная тема.
}
// Proxy Структура содержит указатель на RealSubject изуказатель,это такдляактерское мастерствотема。
type Proxy struct {
realSubject *RealSubject // актерское мастерствотема Включатьодинвернореальностьтемаиз Цитировать,исходныйдля nil。
}
// Proxy из request Метод реализован Subject интерфейсиз request метод.
// Этот метод сначала проверяет realSubject Это ноль, если да, создайте RealSubject из Пример。
// Затем позвоните realSubject из request метод, тем самым косвенно достигая Subject интерфейсиз request метод.
func (p *Proxy) request() {
if p.realSubject == nil { // если realSubject для nil,Пока нет объясненийсоздаватьреальностьтемаиз Пример。
p.realSubject = &RealSubject{} // создавать RealSubject из Пример,и назначьте его realSubject。
}
p.realSubject.request() // вызовреальностьтемаиз request метод.
}
Шаблон команды — это шаблон поведенческого проектирования, который инкапсулирует запрос или операцию в виде объекта. Этот шаблон может разделить отправителя и получателя запроса, чтобы они не взаимодействовали напрямую, а общались косвенно через командный объект.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определить команду интерфейса,Он заявляет, что все конкретные команды должны быть реализованы.изExecuteметод
type Command interface {
Execute() // выполнить командуизметод
}
// определениеReceiverСтруктура,это Волявыполнить командаиз фактического запроса
type Receiver struct{}
func (r *Receiver) Action() {
fmt.Println("Receiver: Action")
}
// Определите структуру ConcreteCommand, которая реализует интерфейс Command.
// каждыйконкретные инструкции都ВключатьодинReceiverиз Цитировать,Запросизполучатель
type ConcreteCommand struct {
receiver *Receiver // Выполнение команды из приемника
}
// ConcreteCommandвыполнитьCommandинтерфейсизExecuteметод
// ДолженметодвызовReceiverизActionметод Приходить Выполнить запрос
func (c *ConcreteCommand) Execute() {
c.receiver.Action() // Выполнить запрос
}
// определениеInvokerСтруктура,Он отвечает за вызов командобъектизExecuteметод
type Invoker struct {
command Command // Сохранить объект команды
}
// вызов命令объектизExecuteметод
func (i *Invoker) Invoke() {
i.command.Execute() // выполнить команду
}
func main() {
// создаватьполучательобъект
receiver := &Receiver{}
// создать конкретный объект команды,и вводитьполучатель
command := &ConcreteCommand{receiver: receiver}
// создаватьзвонящийобъект,и вводитьконкретные инструкцииобъект
invoker := &Invoker{command: command}
// звонящийвыполнить команду
invoker.Invoke() // Выход: Receiver: Action
}
Шаблон «Композит» — это структурный шаблон проектирования, который позволяет объединять объекты в древовидную структуру для представления иерархии «часть-целое». Этот шаблон позволяет пользователям последовательно обрабатывать отдельные объекты и комбинации объектов.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определить интерфейс компонента,делатьдлякомбинациясерединаобъектизпоследовательностьпротокол
type Component interface {
Operation() // осуществлятьдействоватьизметод
Add(Component) // Ккомбинациясерединадобавить в子узелизметод
Remove(Component) // откомбинациясерединаудалить подпискуузелизметод
GetChild(int) Component // Получить дочерние узлы на основе индексаизметод
}
// определениеLeafСтруктура,выражатькомбинациясерединаизлистовой узел
type Leaf struct {
name string
}
// LeafвыполнитьComponentинтерфейсизOperationметод
func (l *Leaf) Operation() {
fmt.Println("Leaf:", l.name)
}
// LeafвыполнитьComponentинтерфейсизAddметод,Листовые узлы не могут иметь дочерних узлов.,потому чтоэто здесь Может Нетвыполнитьили выдать ошибку
func (l *Leaf) Add(c Component) {
fmt.Println("Cannot add to a leaf")
}
// LeafвыполнитьComponentинтерфейсизRemoveметод,Листовые узлы не могут иметь дочерних узлов.,потому чтоэто здесь Может Нетвыполнитьили выдать ошибку
func (l *Leaf) Remove(c Component) {
fmt.Println("Cannot remove from a leaf")
}
// LeafвыполнитьComponentинтерфейсизGetChildметод,Листовые узлы не имеют дочерних узлов,Итак, здесь возвращается ноль
func (l *Leaf) GetChild(i int) Component {
вернуть ноль
}
//Определяем составную структуру,выражатькомбинациясерединаиз Контейнерный узел
type Composite struct {
name string
Children []Component // Сохраните список дочерних узлов
}
// CompositeвыполнитьComponentинтерфейсизOperationметод
func (c *Composite) Operation() {
fmt.Println("Composite:", c.name)
for _, child := range c.Children {
child.Operation() // 递归вызов子узелизOperationметод
}
}
// CompositeвыполнитьComponentинтерфейсизAddметод,КChildrenсписоксерединадобавить в子узел
func (c *Composite) Add(component Component) {
c.Children = append(c.Children, component)
}
// CompositeвыполнитьComponentинтерфейсизRemoveметод,отChildrenсписоксерединаудалить подпискуузел
func (c *Composite) Remove(component Component) {
for i, child := range c.Children {
if child == component {
c.Children = append(c.Children[:i], c.Children[i+1:]...)
break
}
}
}
// CompositeвыполнитьComponentинтерфейсизGetChildметод,Получить дочерние узлы на основе индекса
func (c *Composite) GetChild(i int) Component {
if i < 0 || i >= len(c.Children) {
return nil // Индекс вне диапазона, вернуть ноль
}
return c.Children[i]
}
func main() {
// создаватьлистовой узел
leafA := &Leaf{name: "Leaf A"}
leafB := &Leaf{name: "Leaf B"}
// создатькомбинированный узел
composite := &Composite{name: "Composite Root"}
composite.Add(leafA) // Добавьте листовой узел A в комбинацию
composite.Add(leafB) // Добавьте листовой узел B в композицию.
// осуществлятькомбинацияузелиздействовать
composite.Operation()
}
Шаблон «Итератор» — это шаблон поведенческого проектирования, который позволяет последовательно получать доступ к элементам агрегатного объекта, не раскрывая его внутреннее представление. Шаблон Iterator предоставляет способ перемещения элементов через абстрактные итераторы, позволяя вам перемещаться по коллекции, не зная конкретного типа коллекции.
Функции:
преимущество:
недостаток:
Сценарии применения:
// Определить итератор интерфейса,это声明Понятно Итератор必须выполнитьизNextиCurrentметод
type Iterator interface {
Next() bool // Перейти к следующему элементу и узнать, был ли переход успешным.
Current() interface{} // Вернуть текущий элемент
}
// Определите структуру ConcreteIterator, которая реализует интерфейс Iterator.
type ConcreteIterator struct {
items []string // хранилищеполимеризацияобъектизсписок элементов
index int // Текущая итерация по индексу элемента
}
// Реализация следующего метода, используемая для перехода к следующему элементу.
func (c *ConcreteIterator) Next() bool {
if c.index < len(c.items) {
c.index++ // приращение индекса
return true
}
return false // если Индекс вне диапазона,вернуть ложь
}
// Текущая реализация метода,используется для Вернуть текущий элемент
func (c *ConcreteIterator) Current() interface{} {
if c.index > 0 && c.index <= len(c.items) {
return c.items[c.index-1] // Возвращает текущий индекс элемента
}
return nil // если索引Нетсуществоватьв пределах досягаемости,Возврат ноль
}
//определить агрегатный интерфейс,означает объект агрегации,это Воля Ответственныйсоздавать Итератор
type Aggregate interface {
CreateIterator() Iterator // создать и вернуть итератор
}
// Определите структуру ConcreteAggregate, которая реализует интерфейс Aggregate.
type ConcreteAggregate struct {
items []string // полимеризацияобъектхранилищеизсписок элементов
}
// CreateIteratorметодвыполнить,используется длясоздаватьи вернутьсяодин Итератор
func (a *ConcreteAggregate) CreateIterator() Iterator {
return &ConcreteIterator{items: a.items, index: 0} // возвращатьсяодинновыйиз Итератор Пример
}
func main() {
// создаватьполимеризацияобъект并добавить вэлемент
aggregate := &ConcreteAggregate{items: []string{"Item1", "Item2", "Item3"}}
// использоватьполимеризацияобъектсоздавать Итератор
iterator := aggregate.CreateIterator()
// использовать Итератортраверсполимеризацияобъектсерединаизвсе элементы
for iterator.Next() {
fmt.Println(iterator.Current())
}
}
-End-
Автор оригинала|Ван Шуньчи