[PHP, Go, Параллельное программирование, Изучение языков, Микросервисы] Тонкости реализации Singleton на Golang
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, друзья.
Меня зовут Alex Versus и сегодня с вами посмотрим шаблон Singleton, реализацию на языке Golang.Какая суть?Одиночка - относится к порождающим шаблонам. Гарантирует:
- что у класса/типа есть только один экземпляр
- предоставляет к нему глобальную точку доступа.
Какую задачу решает?Поговорим про задачу, которую решает шаблон. Одиночка решает сразу две проблемы, нарушая принцип единой ответственности (SRP):
- Гарантирует наличие единственного экземпляра объекта. Это полезно для доступа к какому либо общему ресурсу, например к базе данных или при реализации единого механизма изменения свойства, например, уровень звука в эквалайзере.
Представим что у нас есть какой-то объект и через некоторое время вы создаете еще один, но вам хотелось бы получить не новый, а уже созданный объект. Такое поведение невозможно создать с помощью стандартных инструментов, таких как конструктор в объектно-ориентированных языках.
- Предоставить глобальную точку доступа. Обращаю внимание, что это не просто глобальная переменная, через которую можно достучаться до определенного объекта. Глобальная переменная не защищает вас от перезаписи созданного объекта.
Разработчики часто называют Одиночкой объекты, которые выполняют только одну задачу, озвученную выше. Это ошибочное понимание шаблона.Какое решение в Golang?Как решить озвученные задачи в GOlang? Кто знаком с реализацией шаблона в ООП, знают, что нужно скрыть конструктор и объявить публичный статический метод, контролирующий жизненный цикл объекта-одиночки. Статический метод обеспечит доступ к объекту из любого места вашей программы. Реализацию можно посмотреть тут. Сколько бы вы не вызывали данный метод - он всегда вернет один и тот же объект. Диаграмма классов шаблона следующая:
Диаграмма классов SingletonВ GOlang нет классов и конструкторов. Но есть типы и структуры. Как нам реализовать метод getInstance()? Создадим определенный тип singleton:
// declaration defined type
type singleton struct {
}
Инициализируем переменную с типом singleton, равную пустому значению nil:
// declare variable
var instance *singleton = nil
Для установки значения в instance нам нужно воспользоваться методом стандартной библиотеки sync.Once. Он принимает в аргумент функцию, которая отработает один раз за вызов. А так же мы должны с вами определить тип Sigleton и определить в нем интерфейс с методами работы со свойствами нашего типа:
// defined type with interface
type Singleton interface {
// here will be methods
}
И функцию возврата объекта:
/**
Get only one object
*/
func GetInstance() Singleton {
once.Do(func() {
instance = new(singleton)
})
return instance
}
Для проверки реализации мы добавим поле в тип singleton, которое будем менять с помощью сеттеров и геттеров:
// declaration defined type
type singleton struct {
title string
}
И определим в интерфейсе типа Singleton методы, позволяющий изменить значение этого свойства:
// defined type with interface
type Singleton interface {
SetTitle(t string)
GetTitle() string
}
/**
Setter for singleton variable
*/
func (s *singleton) SetTitle(t string) {
s.title = t
}
/**
Getter singleton variable
*/
func (s *singleton) GetTitle() string {
return s.title
}
Реализуем код для работы со свойством данных объектов. Теперь нам надо проверить, как работает наш код. Напишем небольшой тест и увидим, что работает все корректно при такой реализации:
package Singleton
import "testing"
func TestGetInstance(t *testing.T) {
var s Singleton
s = GetInstance()
if s == nil {
t.Fatalf("First sigletone is nil")
}
s.SetTitle("First value")
checkTitle := s.GetTitle()
if checkTitle != "First value" {
t.Errorf("First value is not setted")
}
var s2 Singleton
s2 = GetInstance()
if s2 != s {
t.Error("New instance different")
}
s2.SetTitle("New title")
newTitle := s.GetTitle()
if newTitle != "New title" {
t.Errorf("Title different after change")
}
}
Запускаем код:
go test -v -run TestGetInstance
=== RUN TestGetInstance
--- PASS: TestGetInstance (0.00s)
PASS
ok main/Singleton 0.310s
Отлично! Кажется, что все ок, но на самом деле нет. Хочу показать еще один тест, который покажет, какая проблема существует:
package Singleton
import (
"fmt"
"strconv"
"sync"
"testing"
)
func TestSecondGetInstance(t *testing.T) {
s1 := GetInstance()
s2 := GetInstance()
var w sync.WaitGroup
for i := 0; i < 3000; i++ {
j := i
w.Add(1)
go func() {
t := "title_" + strconv.Itoa(j)
s1.SetTitle(t)
w.Done()
}()
w.Add(1)
go func() {
t2 := "title_2_" + strconv.Itoa(j)
s2.SetTitle(t2)
w.Done()
}()
}
fmt.Println(s1.GetTitle())
fmt.Println(s2.GetTitle())
}
На выходе мы увидим следующее:
go test -v -run TestSecondGetInstance
=== RUN TestSecondGetInstance
title_2998
title_2_2999
Запускается цикл на 3000 итераций в которых создается по две горутины, в которых вызывается метод установки свойства. Метод меняет значения свойств каждую итерацию, используя сеттер. После выполнения блока кода мы ожидаем, что значения свойств объектов-одиночек будут одинаковыми, но это не так. Почему такое происходит? Добавив к команде запуска теста опцию -raсе мы увидим, что происходит так называемая гонка данных. Такое происходит в Golang, когда две горутины одновременно работают с одной и той же переменной и один из потоков пишет в эту переменную. Это проблема многопоточности. Есть несколько способов решить проблему. Мы с вами не будем останавливаться подробно на этой теме, сегодня говорим про реализацию шаблона Singleton. Я просто покажу одно из решений. Решение называется взаимным исключением, достигнуть можно с помощью мьютекса. Условно, классический мьютекс можно представить в виде переменной, которая может находиться в двух состояниях: в заблокированном и в незаблокированном. При входе в свою критическую секцию поток вызывает функцию перевода мьютекса в заблокированное состояние, при этом поток блокируется до освобождения мьютекса, если другой поток уже владеет им. В Go его можно реализовать через стандартную библиотеку в которой есть примитивы синхронизации: sync.Mutex и sync.RWMutex. Реализация выглядит так:
// declaration defined type
type singleton struct {
title string
sync.RWMutex
}
/**
Setter for singleton variable
*/
func (s *singleton) SetTitle(t string) {
s.Lock()
defer s.Unlock()
s.title = t
}
/**
Getter singleton variable
*/
func (s *singleton) GetTitle() string {
s.RLock()
defer s.RUnlock()
return s.title
}
Еще раз запустим второй тест и увидим, что сейчас обе переменные равны:
go test -v -run TestSecondGetInstance
=== RUN TestSecondGetInstance
--- PASS: TestSecondGetInstance (0.00s)
PASS
Вот такая реализация шаблона проектирования Singleton на Golang. В чем преимущества шаблона?
- Гарантируем наличие в программе единственного объекта.
- Предоставляет к нему глобальную точку доступа.
- Реализация отложенной инициализации объекта.
Какие недостатки?
- Нарушает принцип единой ответственности для ООП языков. Каждый объект/класс/тип должен иметь только одну ответственность и соблюдение этой ответственности должно быть инкапсулировано, методы должны решать задачи этой ответственности.
- Маскирует плохой дизайн кода.
- Проблемы мультипоточности для языков типа Golang. Рассмотрели на сегодняшнем уроке один из способов, как решается проблема.
- При автоматизированном тестировании требует созданияmock-объектов. Такие объекты представляют собой конкретную фиктивную реализацию интерфейса, предназначенную исключительно для тестирования и взаимодействия между объектами. В процедурных языках такая конструкция называется dummy.
Singleton — очень обманчивый паттерн, его часто хочется применить, но делать это надо очень осторожно. Например, если мы хотим создать единый регулятор громкости при разработке программного обеспечения для эквалайзера, мы полагаем, что громкость будет являться глобальным свойством программы. Однако если мы захотим ввести отдельный регулятор для громкости каких-нибудь уведомлений — появится второй Singleton, а затем могут появиться зависимости между экземплярами. Нарушения принципа единой обязанности (SRP), согласно которому каждый объект должен иметь только одну обязанность, а все его методы должны быть направленны на ее выполнение. Если мы реализуем Singleton, то наш тип помимо своей основной работы может начать контролировать количество созданных экземпляров. Singleton — глобальный объект, со всеми вытекающими проблемами. Если в программе есть ошибка и она зависит от глобального объекта, состояние которого мог менять кто угодно, то найти ее гораздо сложнее. Или другой пример - мы можем начать писать юнит-тесты, которые всегда зависят друг от друга, если используют один глобальный объект. А на сегодня все. Надеюсь помог вам разобраться в тонкостях реализации практики на языке Golang. Рад был поделиться материалом.
Alex Versus. Всем удачи!
===========
Источник:
habr.com
===========
Похожие новости:
- [Законодательство в IT] Антимонопольное регулирование цифровых платформ в России. Почему ФАС возбудила дело против Google?
- [Информационная безопасность, WordPress, Разработка веб-сайтов, Системное администрирование, Контекстная реклама] WordPress автоматически отключит Google FLoC на веб-сайтах
- [Поисковые технологии, Управление продуктом, Законодательство в IT, IT-компании] Евразийский экономический союз ищет нарушения конкуренции в поисковой выдаче
- [Законодательство в IT, Социальные сети и сообщества, IT-компании] ФАС возбудила дело против Google из-за YouTube
- [Научно-популярное] Новая техника колоризации изменит представление о внешнем виде исторических личностей (перевод)
- [Работа с видео, Python, Обработка изображений, Машинное обучение] Распознавание маски на лице с помощью YOLOv3 (перевод)
- [Разработка веб-сайтов, JavaScript, Конференции, Микросервисы] Frontend Meetup 20/04
- [Изучение языков] Как проверять выражения на английском like a pro
- [Разработка веб-сайтов, PHP, Symfony, Конференции] Говорим, как структурировать код
- Выпуск дистрибутива EndeavourOS 2021.04.17
Теги для поиска: #_php, #_go, #_parallelnoe_programmirovanie (Параллельное программирование), #_izuchenie_jazykov (Изучение языков), #_mikroservisy (Микросервисы), #_php, #_design_patterns, #_design_pattern, #_go, #_golang, #_singleton, #_course, #_php, #_go, #_parallelnoe_programmirovanie (
Параллельное программирование
), #_izuchenie_jazykov (
Изучение языков
), #_mikroservisy (
Микросервисы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 09:39
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, друзья. Меня зовут Alex Versus и сегодня с вами посмотрим шаблон Singleton, реализацию на языке Golang.Какая суть?Одиночка - относится к порождающим шаблонам. Гарантирует:
Диаграмма классов SingletonВ GOlang нет классов и конструкторов. Но есть типы и структуры. Как нам реализовать метод getInstance()? Создадим определенный тип singleton: // declaration defined type
type singleton struct { } // declare variable
var instance *singleton = nil // defined type with interface
type Singleton interface { // here will be methods } /**
Get only one object */ func GetInstance() Singleton { once.Do(func() { instance = new(singleton) }) return instance } // declaration defined type
type singleton struct { title string } // defined type with interface
type Singleton interface { SetTitle(t string) GetTitle() string } /** Setter for singleton variable */ func (s *singleton) SetTitle(t string) { s.title = t } /** Getter singleton variable */ func (s *singleton) GetTitle() string { return s.title } package Singleton
import "testing" func TestGetInstance(t *testing.T) { var s Singleton s = GetInstance() if s == nil { t.Fatalf("First sigletone is nil") } s.SetTitle("First value") checkTitle := s.GetTitle() if checkTitle != "First value" { t.Errorf("First value is not setted") } var s2 Singleton s2 = GetInstance() if s2 != s { t.Error("New instance different") } s2.SetTitle("New title") newTitle := s.GetTitle() if newTitle != "New title" { t.Errorf("Title different after change") } } go test -v -run TestGetInstance
=== RUN TestGetInstance --- PASS: TestGetInstance (0.00s) PASS ok main/Singleton 0.310s package Singleton
import ( "fmt" "strconv" "sync" "testing" ) func TestSecondGetInstance(t *testing.T) { s1 := GetInstance() s2 := GetInstance() var w sync.WaitGroup for i := 0; i < 3000; i++ { j := i w.Add(1) go func() { t := "title_" + strconv.Itoa(j) s1.SetTitle(t) w.Done() }() w.Add(1) go func() { t2 := "title_2_" + strconv.Itoa(j) s2.SetTitle(t2) w.Done() }() } fmt.Println(s1.GetTitle()) fmt.Println(s2.GetTitle()) } go test -v -run TestSecondGetInstance
=== RUN TestSecondGetInstance title_2998 title_2_2999 // declaration defined type
type singleton struct { title string sync.RWMutex } /** Setter for singleton variable */ func (s *singleton) SetTitle(t string) { s.Lock() defer s.Unlock() s.title = t } /** Getter singleton variable */ func (s *singleton) GetTitle() string { s.RLock() defer s.RUnlock() return s.title } go test -v -run TestSecondGetInstance
=== RUN TestSecondGetInstance --- PASS: TestSecondGetInstance (0.00s) PASS
Alex Versus. Всем удачи! =========== Источник: habr.com =========== Похожие новости:
Параллельное программирование ), #_izuchenie_jazykov ( Изучение языков ), #_mikroservisy ( Микросервисы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 09:39
Часовой пояс: UTC + 5