предисловие
предыдущий пост«23 шаблона проектирования с использованием Go для реализации GoF (2)», мы представилиСтруктурный образец(Структурный шаблон) шаблон композиции, шаблон адаптера и шаблон моста. В этой статье будут представлены оставшиеся структурные шаблоны, шаблон прокси, шаблон украшения, шаблон внешнего вида и шаблон легковеса. В этой статье в качестве примера будет по-прежнему использоваться система обработки сообщений. Если пример не ясен, переместите«23 шаблона проектирования с использованием Go для реализации GoF (1)»а также«23 шаблона проектирования с использованием Go для реализации GoF (2)»Узнайте о его разработке и реализации.
Шаблон прокси
Введение
Шаблон прокси предоставляет объекту прокси для управления доступом к этому объекту., это очень используемый шаблон проектирования, даже в реальной жизни, он очень распространен, например, у спекулянтов билетами на концерты. Допустим, вам нужно посмотреть концерт, но билеты на официальном сайте распроданы, поэтому я в тот же день пошел на сцену, чтобы купить один по высокой цене через спекулянтов. В этом примере скальперы эквивалентны агенту билетов на концерты, В случае, если билеты не могут быть приобретены через официальные каналы, вы выполнили цель через агента.
На примере билетов на концерты мы также можем видеть, что ключом к использованию прокси-модели являетсяКогда Клиенту неудобно напрямую обращаться к объекту, предоставьте прокси-объект для управления доступом к объекту.. Клиент фактически обращается к прокси-объекту, и прокси-объект передает запрос клиента объекту онтологии для обработки.
В программировании режим прокси также делится на несколько типов:
1,удаленный прокси(удаленный прокси), удаленный прокси подходит для объекта, который предоставляет услугу на удаленной машине.Услугу нельзя использовать через обычные вызовы функций, и это нужно делать через удаленный прокси.Поскольку к объекту онтологии нельзя получить прямой доступ, все удаленные прокси-объекты обычно не содержат напрямую ссылку на объект онтологии, а содержат адрес удаленной машины и получают доступ к объекту онтологии через сетевой протокол..
2,виртуальный агент(виртуальный прокси), в дизайне программы часто используются тяжеловесные сервисные объекты. Если экземпляр объекта удерживается все время, он будет потреблять много системных ресурсов. В это время виртуальный прокси можно использовать для задержки инициализации объект.
3.агент защиты(защитный прокси), защитный прокси используется для управления доступом к объекту онтологии и часто используется в сценариях, где к доступу клиента необходимо добавить проверку разрешений.
4.кеширующий прокси(кеширующий прокси), кэширующий прокси в основном добавляет слой кеша между клиентом и объектом онтологии для ускорения доступа к объекту онтологии, что является обычным в сценарии подключения к базе данных.
5.умное цитирование(интеллектуальная ссылка), интеллектуальная ссылка обеспечивает дополнительные действия для доступа к объекту онтологии.Общей реализацией является интеллектуальный указатель в C++, который обеспечивает функцию подсчета для доступа к объекту.Когда количество доступного объекта равно 0 , объект уничтожен.
Все эти агенты имеют одинаковый принцип реализации.Ниже мы представим реализацию удаленного агента на языке Go.
Перейти к реализации
Учтите, что выходные данные системы обработки сообщений хранятся в базе данных, а интерфейс базы данных выглядит следующим образом:
package db
...
// Key-Value数据库接口
type KvDb interface {
// 存储数据
// 其中reply为操作结果,存储成功为true,否则为false
// 当连接数据库失败时返回error,成功则返回nil
Save(record Record, reply *bool) error
// 根据key获取value,其中value通过函数参数中指针类型返回
// 当连接数据库失败时返回error,成功则返回nil
Get(key string, value *string) error
}
type Record struct {
Key string
Value string
}
База данных представляет собой базу данных Key-Value, использующуюmap
Храните данные, ниже приведена реализация базы данных на стороне сервера,db.Server
Достигнутоdb.KvDb
интерфейс:
package db
...
// 数据库服务端实现
type Server struct {
// 采用map存储key-value数据
data map[string]string
}
func (s *Server) Save(record Record, reply *bool) error {
if s.data == nil{
s.data = make(map[string]string)
}
s.data[record.Key] = record.Value
*reply = true
return nil
}
func (s *Server) Get(key string, reply *string) error {
val, ok := s.data[key]
if !ok {
*reply = ""
return errors.New("Db has no key " + key)
}
*reply = val
return nil
}
Система обработки сообщений и база данных находятся на разных компьютерах, поэтому система обработки сообщений не может напрямую вызыватьdb.Server
Метод хранения данных, такой как сценарий, когда поставщик услуг и потребитель услуг не находятся на одном компьютере, с использованием удаленного прокси-сервера является более подходящим.
Одной из наиболее распространенных реализаций удаленных прокси являетсяудаленный вызов процедур(Удаленный вызов процедуры, называемыйRPC), что позволяет клиентскому приложению напрямую вызывать метод серверного приложения на другом компьютере, как если бы это был локальный объект. В области языка го, помимо знаменитогоgRPC, стандартная библиотека Gonet/rpc
Пакет также предоставляет реализацию RPC. Ниже мы проходимnet/rpc
Возможность предоставления внешнего сервера базы данных:
package db
...
// 启动数据库,对外提供RPC接口进行数据库的访问
func Start() {
rpcServer := rpc.NewServer()
server := &Server{data: make(map[string]string)}
// 将数据库接口注册到RPC服务器上
if err := rpcServer.Register(server); err != nil {
fmt.Printf("Register Server to rpc failed, error: %v", err)
return
}
l, err := net.Listen("tcp", "127.0.0.1:1234")
if err != nil {
fmt.Printf("Listen tcp failed, error: %v", err)
return
}
go rpcServer.Accept(l)
time.Sleep(1 * time.Second)
fmt.Println("Rpc server start success.")
}
До сих пор мы предоставили внешний доступ к базе данных. Теперь нам нужен удаленный прокси для подключения к серверу базы данных и выполнения связанных операций с базой данных. Для системы обработки сообщений ей не нужны и не должны быть известны низкоуровневые подробности взаимодействия между удаленным агентом и сервером базы данных, что может уменьшить связанность между системами. Следовательно, удаленный прокси должен реализоватьdb.KvDb
:
package db
...
// 数据库服务端远程代理,实现db.KvDb接口
type Client struct {
// RPC客户端
cli *rpc.Client
}
func (c *Client) Save(record Record, reply *bool) error {
var ret bool
// 通过RPC调用服务端的接口
err := c.cli.Call("Server.Save", record, &ret)
if err != nil {
fmt.Printf("Call db Server.Save rpc failed, error: %v", err)
*reply = false
return err
}
*reply = ret
return nil
}
func (c *Client) Get(key string, reply *string) error {
var ret string
// 通过RPC调用服务端的接口
err := c.cli.Call("Server.Get", key, &ret)
if err != nil {
fmt.Printf("Call db Server.Get rpc failed, error: %v", err)
*reply = ""
return err
}
*reply = ret
return nil
}
// 工厂方法,返回远程代理实例
func CreateClient() *Client {
rpcCli, err := rpc.Dial("tcp", "127.0.0.1:1234")
if err != nil {
fmt.Printf("Create rpc client failed, error: %v.", err)
return nil
}
return &Client{cli: rpcCli}
}
как удаленный проксиdb.Client
напрямую не держалdb.Server
, но держитсяip:port
, методы которого были вызваны RPC-клиентом.
Далее нам нужно реализовать новыйOutput
плагинDbOutput
,перечислитьdb.Client
Удаленный брокер, который хранит сообщения в базе данных.
существует«23 шаблона проектирования с использованием Go для реализации GoF (2)»в нашем
Plugin
Три способа представить жизненный циклStart
,Stop
,Status
После этого каждый раз, когда добавляется новый плагин, необходимо реализовать эти три метода. Однако логика этих трех методов большинства плагинов в основном одинакова, что приводит к определенной степени избыточности кода. Что является хорошим решением проблемы дублирования кода?Комбинированный режим!Затем мы используем шаблон композиции, чтобы извлечь этот метод в новый объект.
LifeCycle
, при добавлении плагина просто добавьтеLifeCycle
как анонимный участник (Встроить комбинацию) для решения проблемы избыточного кода.package plugin ... type LifeCycle struct { name string status Status } func (l *LifeCycle) Start() { l.status = Started fmt.Printf("%s plugin started.\n", l.name) } func (l *LifeCycle) Stop() { l.status = Stopped fmt.Printf("%s plugin stopped.\n", l.name) } func (l *LifeCycle) Status() Status { return l.status }
DbOutput
Реализация такова, он содержит удаленный агент, через который сообщение сохраняется в удаленной базе данных.
package plugin
...
type DbOutput struct {
LifeCycle
// 操作数据库的远程代理
proxy db.KvDb
}
func (d *DbOutput) Send(msg *msg.Message) {
if d.status != Started {
fmt.Printf("%s is not running, output nothing.\n", d.name)
return
}
record := db.Record{
Key: "db",
Value: msg.Body.Items[0],
}
reply := false
err := d.proxy.Save(record, &reply)
if err != nil || !reply {
fmt.Println("Save msg to db server failed.")
}
}
func (d *DbOutput) Init() {
d.proxy = db.CreateClient()
d.name = "db output"
}
Код теста выглядит следующим образом:
package test
...
func TestDbOutput(t *testing.T) {
db.Start()
config := pipeline.Config{
Name: "pipeline3",
Input: plugin.Config{
PluginType: plugin.InputType,
Name: "hello",
},
Filter: plugin.Config{
PluginType: plugin.FilterType,
Name: "upper",
},
Output: plugin.Config{
PluginType: plugin.OutputType,
Name: "db",
},
}
p := pipeline.Of(config)
p.Start()
p.Exec()
// 验证DbOutput存储的正确性
cli := db.CreateClient()
var val string
err := cli.Get("db", &val)
if err != nil {
t.Errorf("Get db failed, error: %v\n.", err)
}
if val != "HELLO WORLD" {
t.Errorf("expect HELLO WORLD, but actual %s.", val)
}
}
// 运行结果
=== RUN TestDbOutput
Rpc server start success.
db output plugin started.
upper filter plugin started.
hello input plugin started.
Pipeline started.
--- PASS: TestDbOutput (1.01s)
PASS
Шаблон декоратора
Введение
В программировании нам часто нужно добавлять новые поведения к объектам.Первой идеей многих студентов является расширение объекта онтологии и достижение цели посредством наследования. Однако использование наследования неизбежно имеет следующие два недостатка: (1) Наследование является статическим, оно определяется во время компиляции, и поведение объектов не может быть изменено во время выполнения. (2) Подкласс может иметь только один родительский класс.Когда нужно добавить слишком много новых функций, легко вызвать резкое увеличение числа классов.
Для этого сценария мы обычно используеморнамент(Шаблон декоратора) решить,Он использует композицию вместо наследования и может динамически накладывать новое поведение на объекты онтологии.. Теоретически, пока нет ограничений, он может сохранять функции стекирования. Наиболее классическим применением режима оформления является система потоков ввода/вывода Java.С помощью режима оформления пользователи могут динамически добавлять функции к исходным потокам ввода и вывода, такие как ввод и вывод в соответствии со строками, добавление кэша и т. д., чтобы целые системы потокового ввода-вывода обладают высокой масштабируемостью и гибкостью.
Со структурной точки зрения узор украшения и узор-заместитель имеют большое сходство, но точки, подчеркнутые ими, различаются.Первый делает упор на добавление новых функций к объектам онтологии, а второй делает упор на контроль доступа к объектам онтологии.. Конечно, умные ссылки в шаблоне прокси, на мой взгляд, точно такие же, как в шаблоне декоратора.
Перейти к реализации
Рассмотрите возможность добавления такой функции в систему обработки сообщений для подсчета количества сообщений, сгенерированных каждым источником ввода сообщений, то есть для подсчета каждого сообщения отдельно.Input
производитьMessage
количество. Проще всего это сделать в каждомInput
изReceive
В методе выполняется точечная статистика, но это приведет к связыванию статистического кода и бизнес-кода. Если статистическая логика изменится, она произведетМодификация дробовика,вместе сInput
По мере увеличения количества типов соответствующий код будет становиться все труднее поддерживать.
Лучший подход — разместить логику статистики в одном месте и вызывать ее при каждом вызове.Input
изReceive
Точечная статистика после метода. И это как раз для декоративного узора, дляInput
(объект онтологии) для предоставления функции Точечной статистики (новое поведение). мы можем разработатьInputMetricDecorator
в видеInput
Декоратор завершает логику подсчета статистики в декораторе.
Во-первых, нам нужно разработать статистическийInput
производитьMessage
Ряд объектов, которые должны быть глобально уникальными, поэтому они реализованы с использованием паттерна singleton:
package metric
...
// 消息输入源统计,设计为单例
type input struct {
// 存放统计结果,key为Input类型如hello、kafka
// value为对应Input的消息统计
metrics map[string]uint64
// 统计打点时加锁
mu *sync.Mutex
}
// 给名称为inputName的Input消息计数加1
func (i *input) Inc(inputName string) {
i.mu.Lock()
defer i.mu.Unlock()
if _, ok := i.metrics[inputName]; !ok {
i.metrics[inputName] = 0
}
i.metrics[inputName] = i.metrics[inputName] + 1
}
// 输出当前所有打点的情况
func (i *input) Show() {
fmt.Printf("Input metric: %v\n", i.metrics)
}
// 单例
var inputInstance = &input{
metrics: make(map[string]uint64),
mu: &sync.Mutex{},
}
func Input() *input {
return inputInstance
}
Далее приступаем к реализацииInputMetricDecorator
, он достигаетInput
интерфейс и содержит объект онтологииInput
. существуетInputMetricDecorator
существуетReceive
тело вызова методаInput
изReceive
метод и выполните статистическое действие.
package plugin
...
type InputMetricDecorator struct {
input Input
}
func (i *InputMetricDecorator) Receive() *msg.Message {
// 调用本体对象的Receive方法
record := i.input.Receive()
// 完成统计逻辑
if inputName, ok := record.Header.Items["input"]; ok {
metric.Input().Inc(inputName)
}
return record
}
func (i *InputMetricDecorator) Start() {
i.input.Start()
}
func (i *InputMetricDecorator) Stop() {
i.input.Stop()
}
func (i *InputMetricDecorator) Status() Status {
return i.input.Status()
}
func (i *InputMetricDecorator) Init() {
i.input.Init()
}
// 工厂方法, 完成装饰器的创建
func CreateInputMetricDecorator(input Input) *InputMetricDecorator {
return &InputMetricDecorator{input: input}
}
Наконец, мыPipeline
В фабричном методе добавьте тело Input вInputMetricDecorator
играет роль:
package 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)
// 为本体Input加上InputMetricDecorator装饰器
p.input = plugin.CreateInputMetricDecorator(p.input)
return p
}
Код теста выглядит следующим образом:
package test
...
func TestInputMetricDecorator(t *testing.T) {
p1 := pipeline.Of(pipeline.HelloConfig())
p2 := pipeline.Of(pipeline.KafkaInputConfig())
p1.Start()
p2.Start()
p1.Exec()
p2.Exec()
p1.Exec()
metric.Input().Show()
}
// 运行结果
=== RUN TestInputMetricDecorator
Console output plugin started.
Upper filter plugin started.
Hello input plugin started.
Pipeline started.
Console output plugin started.
Upper filter plugin started.
Kafka input plugin started.
Pipeline started.
Output:
Header:map[content:text input:hello], Body:[HELLO WORLD]
Output:
Header:map[content:text input:kafka], Body:[I AM MOCK CONSUMER.]
Output:
Header:map[content:text input:hello], Body:[HELLO WORLD]
Input metric: map[hello:2 kafka:1]
--- PASS: TestInputMetricProxy (0.00s)
PASS
Фасадный узор
Введение
Со структурной точки зрения режим внешнего вида очень прост, в основном этоПредоставляет унифицированный внешний интерфейс более высокого уровня для подсистемы, чтобы Клиент мог более удобно использовать функции подсистемы.. На рисунке класс подсистемы — это аббревиатура объекта в подсистеме, который может быть объектом или набором десятков объектов. Режим внешнего вида уменьшает связь между Клиентом и Подсистемой, пока Фасад остается неизменным, как бы ни менялась Подсистема, это незаметно для Клиента.
Режим внешнего вида часто используется в программировании, например, мы нажимаем на торговый центр购买
кнопка, для покупателей, только см.购买
Это единый интерфейс, но для системы торгового центра ряд бизнес-процессов выполняется внутри, например, проверка запасов, обработка заказов, оплата, логистика и так далее. Фасадный режим значительно улучшает взаимодействие с пользователем и освобождает пользователей от сложных бизнес-процессов.
Внешний вид часто используется вМногоуровневая архитектураВыше мы обычно предоставляем один или несколько унифицированных внешних интерфейсов доступа для каждого уровня многоуровневой архитектуры, что может уменьшить связь между различными уровнями и сделать системную архитектуру более разумной.
Перейти к реализации
Режим внешнего вида также очень прост в реализации, или рассмотрим предыдущую систему обработки сообщений. существуетPipeline
, каждое сообщение будет проходить по очередиInput->Filter->OutputКод реализован так:
p := pipeline.Of(config)
message := p.input.Receive()
message = p.filter.Process(message)
p.output.Send(message)
Однако дляPipeline
Для пользователя сообщения он может не заботиться о конкретном потоке обработки сообщения, ему нужно только знать, что сообщение прошло черезPipeline
Просто смирись с этим. Поэтому нам нужно спроектировать простой внешний интерфейс:
package pipeline
...
func (p *Pipeline) Exec() {
msg := p.input.Receive()
msg = p.filter.Process(msg)
p.output.Send(msg)
}
Таким образом, пользователь просто вызываетExec
метод, вы можете завершить обработку сообщения, тестовый код выглядит следующим образом:
package test
...
func TestPipeline(t *testing.T) {
p := pipeline.Of(pipeline.HelloConfig())
p.Start()
// 调用Exec方法完成一次消息的处理
p.Exec()
}
// 运行结果
=== RUN TestPipeline
console output plugin started.
upper filter plugin started.
hello input plugin started.
Pipeline started.
Output:
Header:map[content:text input:hello], Body:[HELLO WORLD]
--- PASS: TestPipeline (0.00s)
PASS
Модель наилегчайшего веса
Введение
В программировании мы часто сталкиваемся с некоторыми очень тяжелыми объектами, которые обычно имеют много свойств-членов.Когда система заполнена большим количеством этих объектов, память системы будет находиться под огромным давлением. Кроме того, частое создание этих объектов также сильно потребляет ЦП системы. Во многих случаях большинство свойств членов этих тяжелых объектов фиксированы.В этом сценарии вы можете использоватьнаилегчайший образецОптимизируйте и спроектируйте фиксированную часть как общий объект (облегченный, легковесный), что может сэкономить много системной памяти и ЦП.
Шаблон Flyweight избавляет от необходимости хранить все данные в каждом объекте, позволяя вам загружать больше объектов в ограниченный объем памяти, разделяя одно и то же состояние, общее для нескольких объектов..
Когда мы решаем оптимизировать тяжелый объект с помощью шаблона легковеса, нам сначала нужно разделить свойства тяжелого объекта на две категории: те, которые можно использовать совместно, и те, которые нельзя использовать совместно. Первый мы называемвнутреннее состояние(внутреннее состояние), которое хранится в Приспособленце и не меняется в зависимости от контекста, в котором находится Приспособленец; последнее называетсявнешнее состояние(внешнее состояние), значение которого зависит от контекста, в котором находится Приспособленец, и поэтому не может использоваться совместно. Например, и статья A, и статья B относятся к изображению A. Поскольку текстовое содержание статьи A и статьи B различно, текст является внешним состоянием и не может использоваться совместно, однако изображение A, на которое они ссылаются, является одним и тем же и принадлежит внутреннему состоянию, поэтому рисунок А можно представить как легковес
заводской узорОбычно в сочетании с режимом Flyweight Factory предоставляет единственный интерфейс для получения объектов Flyweight, так что клиент не может понять, как распределяется Flyweight, что уменьшает связанность модулей. режим наилегчайшего веса иодноэлементный шаблонЕсть некоторое сходство в совместном использовании объектов в системе, но одноэлементный шаблон больше касаетсяОбъекты создаются только один раз в системе, в то время как модель наилегчайшего веса больше связана сКак разделить одно и то же состояние на несколько объектов.
Перейти к реализации
Теперь предположим, что вам нужно разработать систему для записи информации об игроках, командах и результатах игр в НБА.
командаTeam
Структура данных определяется следующим образом:
package nba
...
type TeamId uint8
const (
Warrior TeamId = iota
Laker
)
type Team struct {
Id TeamId // 球队ID
Name string // 球队名称
Players []*Player // 球队中的球员
}
игрокPlayer
Структура данных определяется следующим образом:
package nba
...
type Player struct {
Name string // 球员名字
Team TeamId // 球员所属球队ID
}
Результаты матчаMatch
Структура данных определяется следующим образом:
package nba
...
type Match struct {
Date time.Time // 比赛时间
LocalTeam *Team // 主场球队
VisitorTeam *Team // 客场球队
LocalScore uint8 // 主场球队得分
VisitorScore uint8 // 客场球队得分
}
func (m *Match) ShowResult() {
fmt.Printf("%s VS %s - %d:%d\n", m.LocalTeam.Name, m.VisitorTeam.Name,
m.LocalScore, m.VisitorScore)
}
Игра в НБА состоит из двух команд, команды хозяев и команды гостей, чтобы завершить игру, соответствующий код,Match
Экземпляр будет содержать 2Team
пример. В настоящее время в НБА насчитывается в общей сложности 30 команд.По данным каждой команды, сыгравшей 82 игры регулярного чемпионата в каждом сезоне, всего будет проведено 2460 игр за сезон.Соответственно, будет проведено 4920 игр.Team
пример. Тем не менее, 30 команд НБА фиксированы, на самом деле только 30.Team
Инстанс может полностью записывать всю игровую информацию сезона, а оставшиеся 4890Team
Экземпляры являются избыточными данными.
В этом случае для оптимизации целесообразно использовать режим Flyweight.Team
предназначен для несколькихMatch
Легковес между экземплярами. Приобретение Flyweight осуществляется через фабрику Flyweight, фабрику Flyweight.teamFactory
Определение выглядит следующим образом, и Клиент использует его единообразноteamFactory.TeamOf
способ получить командуTeam
пример. Из них каждая командаTeam
Экземпляр будет создан только один раз, а затем добавлен в пул команды.Последующие приобретения будут получены непосредственно из пула, таким образом, будет достигнута цель совместного использования.
package nba
...
type teamFactory struct {
// 球队池,缓存球队实例
teams map[TeamId]*Team
}
// 根据TeamId获取Team实例,从池中获取,如果池里没有,则创建
func (t *teamFactory) TeamOf(id TeamId) *Team {
team, ok := t.teams[id]
if !ok {
team = createTeam(id)
t.teams[id] = team
}
return team
}
// 享元工厂的单例
var factory = &teamFactory{
teams: make(map[TeamId]*Team),
}
func Factory() *teamFactory {
return factory
}
// 根据TeamId创建Team实例,只在TeamOf方法中调用,外部不可见
func createTeam(id TeamId) *Team {
switch id {
case Warrior:
w := &Team{
Id: Warrior,
Name: "Golden State Warriors",
}
curry := &Player{
Name: "Stephen Curry",
Team: Warrior,
}
thompson := &Player{
Name: "Klay Thompson",
Team: Warrior,
}
w.Players = append(w.Players, curry, thompson)
return w
case Laker:
l := &Team{
Id: Laker,
Name: "Los Angeles Lakers",
}
james := &Player{
Name: "LeBron James",
Team: Laker,
}
davis := &Player{
Name: "Anthony Davis",
Team: Laker,
}
l.Players = append(l.Players, james, davis)
return l
default:
fmt.Printf("Get an invalid team id %v.\n", id)
return nil
}
}
Код теста выглядит следующим образом:
package test
...
func TestFlyweight(t *testing.T) {
game1 := &nba.Match{
Date: time.Date(2020, 1, 10, 9, 30, 0, 0, time.Local),
LocalTeam: nba.Factory().TeamOf(nba.Warrior),
VisitorTeam: nba.Factory().TeamOf(nba.Laker),
LocalScore: 102,
VisitorScore: 99,
}
game1.ShowResult()
game2 := &nba.Match{
Date: time.Date(2020, 1, 12, 9, 30, 0, 0, time.Local),
LocalTeam: nba.Factory().TeamOf(nba.Laker),
VisitorTeam: nba.Factory().TeamOf(nba.Warrior),
LocalScore: 110,
VisitorScore: 118,
}
game2.ShowResult()
// 两个Match的同一个球队应该是同一个实例的
if game1.LocalTeam != game2.VisitorTeam {
t.Errorf("Warrior team do not use flyweight pattern")
}
}
// 运行结果
=== RUN TestFlyweight
Golden State Warriors VS Los Angeles Lakers - 102:99
Los Angeles Lakers VS Golden State Warriors - 110:118
--- PASS: TestFlyweight (0.00s)
Суммировать
В этой статье мы в основном вводим шаблон прокси, шаблон украшения, шаблон внешнего вида и шаблон легковеса в структурном шаблоне.прокси-режимПредоставлять прокси для объекта для управления доступом к объекту, подчеркивая контроль доступа к объекту онтологии;орнаментВозможность динамического наложения новых поведений для объектов онтологии, уделяя особое внимание добавлению новых функций к объектам онтологии;Внешний вид РежимОбеспечивает унифицированный внешний интерфейс более высокого уровня для подсистем с упором на многоуровневость и развязку;наилегчайший образецУменьшите потребление ресурсов системы за счет совместного использования объектов, подчеркнув, как разделить одно и то же состояние между несколькими объектами.
На данный момент представлены все 7 структурных паттернов.В следующей статье мы начнем знакомить с последним типом паттерна проектирования —поведенческая модель(Поведенческий паттерн).