Объектно-ориентированная модель отличается от традиционных ООП-языков, таких как C# или Java. Нет классов и наследования в привычном понимании. Вместо этого используются структуры (structs) и интерфейсы для реализации основных принципов ООП: инкапсуляции, композиции и полиморфизма.
Служат аналогом классов. Они позволяют объединять данные в логически связанные группы.
type Person struct {
Name string
Age int
}
Могут быть определены для структур, что позволяет связывать функции с типами.
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
Достигается через модификаторы доступа на уровне пакета. Поля и методы, начинающиеся с заглавной буквы, экспортируемые (public), остальные — нет (private).
type Person struct {
name string // неэкспортируемое поле
Age int // экспортируемое поле
}
Go не поддерживает классическое наследование. Вместо этого используется композиция для включения функциональности одного типа в другой.
type Employee struct {
Person
Position string
}
Используются для определения поведения. Типы могут реализовывать интерфейсы неявно, просто предоставляя методы, указанные в интерфейсе.
type Greeter interface {
Greet()
}
func SayHello(g Greeter) {
g.Greet()
}
type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
func main() {
p := Person{Name: "John"}
SayHello(p)
}
Достигается через интерфейсы. Любой тип, который реализует интерфейс, может быть использован вместо него.
type Greeter interface {
Greet()
}
func SayHello(g Greeter) {
g.Greet()
}
type Robot struct {
ID string
}
func (r Robot) Greet() {
fmt.Printf("Greetings, I am robot %s\n", r.ID)
}
func main() {
p := Person{Name: "John"}
r := Robot{ID: "XJ-9"}
SayHello(p)
SayHello(r)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Реализуют разные концепции и обладают различными характеристиками. Встраивание используется для композиции и повторного использования кода, а не для создания иерархий классов, как в традиционном наследовании.
Наследование: В языках с традиционным ООП, таких как C# или Java, наследование создает иерархии классов, где подклассы наследуют свойства и методы суперклассов. Подклассы могут переопределять методы суперклассов (полиморфизм) и добавлять новые методы.
Встраивание: Go не поддерживает иерархии классов. Встраивание позволяет включать типы в другие типы без создания строгих иерархий. Встраивание не создает отношений "is-a" (как в наследовании), а реализует отношения "has-a". Здесь
Employee
наследует свойства и методы Person
.public class Person {
public string Name { get; set; }
public void Greet() {
Console.WriteLine($"Hello, my name is {Name}");
}
}
public class Employee : Person {
public string Position { get; set; }
}
Здесь
Employee
включает Person
как встроенное поле, но не наследует его в традиционном смысле.type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
type Employee struct {
Person
Position string
}
func main() {
e := Employee{Person: Person{Name: "John"}, Position: "Developer"}
e.Greet() // Вызов метода Greet из встроенного типа Person
fmt.Println("Position:", e.Position)
}
Наследование: Создает жесткую связь между классами, что может привести к хрупким и негибким иерархиям. Подклассы зависят от реализации суперклассов, что может привести к проблемам при изменении суперклассов.
Встраивание: Используется композиция, что способствует более гибкой и модульной архитектуре. Встроенные типы могут быть заменены или изменены без изменения иерархии.
Наследование: Подклассы могут переопределять методы суперклассов, что является основой для полиморфизма.
Встраивание: методы встроенных типов могут быть "переопределены" путем объявления методов с теми же именами в внешнем типе, но это не является полноценным переопределением как в наследовании. Вызов методов не поддерживает виртуальные методы как в C# или Java.
type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
type Employee struct {
Person
}
func (e Employee) Greet() {
fmt.Println("Hello, I am an employee")
}
func main() {
e := Employee{Person: Person{Name: "John"}}
e.Greet() // Вызов метода Greet из Employee
e.Person.Greet() // Вызов метода Greet из встроенного типа Person
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Существует несколько способов написания обобщенного кода. До выхода Go 1.18, который представил встроенную поддержку обобщений (generics), разработчики использовали различные техники для достижения аналогичного эффекта.
С выходом Go 1.18 язык получил встроенную поддержку обобщений, позволяющую создавать обобщенные функции и типы. Это позволяет писать код, который работает с любыми типами, определенными параметрами.
package main
import "fmt"
func Map[T any](arr []T, f func(T) T) []T {
result := make([]T, len(arr))
for i, v := range arr {
result[i] = f(v)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4}
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2, 4, 6, 8]
}
Пример обобщенного типа:
package main
import "fmt"
type Pair[T any, U any] struct {
First T
Second U
}
func main() {
p := Pair[int, string]{First: 1, Second: "one"}
fmt.Println(p) // {1 one}
}
До появления обобщений, интерфейсы были основным способом достижения обобщенности. Интерфейсы позволяют определять функции, которые могут работать с любыми типами, реализующими определенные методы.
package main
import "fmt"
type Stringer interface {
String() string
}
func Print(s Stringer) {
fmt.Println(s.String())
}
type Person struct {
Name string
}
func (p Person) String() string {
return p.Name
}
func main() {
p := Person{Name: "Alice"}
Print(p) // Alice
}
Пустой интерфейс (
interface{}
) может содержать значения любого типа. Это позволяет создавать функции, принимающие значения любых типов, но требует явного приведения типов.package main
import "fmt"
func Print(v interface{}) {
fmt.Println(v)
}
func main() {
Print(42)
Print("Hello")
Print([]int{1, 2, 3})
}
Рефлексия позволяет программам исследовать и изменять структуру и поведение объектов во время выполнения. Это мощный, но сложный способ написания обобщенного кода.
package main
import (
"fmt"
"reflect"
)
func Print(v interface{}) {
rv := reflect.ValueOf(v)
fmt.Println("Type:", rv.Type(), "Value:", rv)
}
func main() {
Print(42) // Type: int Value: 42
Print("Hello") // Type: string Value: Hello
Print([]int{1, 2, 3}) // Type: []int Value: [1 2 3]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Контексты (
context.Context
) широко используются для передачи метаданных, управления временем выполнения и отмены операций в многопоточной среде. Пакет context
предоставляет несколько видов контекстов, каждый из которых предназначен для различных сценариев использования. context.Background()
возвращает пустой контекст, который обычно используется как корневой контекст в программах. Он не имеет отмены или дедлайна и не содержит значений.ctx := context.Background()
context.TODO()
также возвращает пустой контекст и используется в случаях, когда еще не ясно, какой контекст следует использовать. Это временный заполнитель, который можно заменить на другой контекст позже.ctx := context.TODO()
context.WithCancel(parent Context)
создает дочерний контекст, который может быть отменен явно вызовом функции отмены (cancel
). Это полезно для контроля выполнения горутин.ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
// Работа горутины
<-ctx.Done() // Ожидание отмены контекста
fmt.Println("Goroutine canceled")
}()
// Отмена контекста после некоторого времени
time.Sleep(2 * time.Second)
cancel()
context.WithDeadline(parent Context, d time.Time)
создает дочерний контекст, который будет автоматически отменен по истечении заданного времени (дедлайна).deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
select {
case <-time.After(6 * time.Second):
fmt.Println("Done")
case <-ctx.Done():
fmt.Println("Context canceled:", ctx.Err())
}
context.WithTimeout(parent Context, timeout time.Duration)
создает дочерний контекст, который будет автоматически отменен через заданное время (таймаут). Это упрощенный вариант WithDeadline
.ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
select {
case <-time.After(6 * time.Second):
fmt.Println("Done")
case <-ctx.Done():
fmt.Println("Context canceled:", ctx.Err())
}
context.WithValue(parent Context, key, val interface{})
создает дочерний контекст, который несет значение, связанное с заданным ключом. Это полезно для передачи метаданных между функциями.type key string
func main() {
ctx := context.WithValue(context.Background(), key("userID"), 12345)
process(ctx)
}
func process(ctx context.Context) {
if v := ctx.Value(key("userID")); v != nil {
fmt.Println("UserID:", v)
} else {
fmt.Println("UserID not found")
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Контекст (
context.Context
) используется для управления временем выполнения, обмена метаданными и отмены операций. Контексты задают таймауты и дедлайны, автоматически отменяя операции по истечении времени.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
Контексты позволяют явно отменять операции, что полезно для управления горутинами.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine canceled")
return
default:
fmt.Println("Goroutine working")
time.Sleep(500 * time.Millisecond)
}
}
}(ctx)
time.Sleep(2 * time.Second)
cancel()
time.Sleep(1 * time.Second)
Контексты позволяют передавать метаданные между функциями.
type key string
ctx := context.WithValue(context.Background(), key("userID"), 12345)
process(ctx)
func process(ctx context.Context) {
userID := ctx.Value(key("userID")).(int)
fmt.Println("UserID:", userID)
}
Контексты управляют жизненным циклом запросов, обеспечивая таймауты и отмену при завершении запросов.
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
fmt.Println("Handler started")
defer fmt.Println("Handler ended")
select {
case <-time.After(5 * time.Second):
fmt.Fprintln(w, "Request processed")
case <-ctx.Done():
err := ctx.Err()
fmt.Println("Handler error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
Контексты синхронизируют горутины и управляют их завершением.
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
worker(ctx, "Worker 1")
}()
go func() {
defer wg.Done()
worker(ctx, "Worker 2")
}()
wg.Wait()
func worker(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "stopped")
return
default:
fmt.Println(name, "working")
time.Sleep(1 * time.Second)
}
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Микросервисы и монолиты — это два разных подхода к архитектуре программного обеспечения. Разница между ними заключается в способе организации кода и развертывания приложений.
Монолит — это приложение, в котором весь код собран в один единый блок. Все компоненты системы, включая пользовательский интерфейс, серверную часть, бизнес-логику и базу данных, интегрированы в один крупный исполняемый файл или модуль.
Микросервисы — это подход, при котором приложение разбивается на несколько независимых сервисов. Каждый микросервис отвечает за конкретную бизнес-функцию и взаимодействует с другими сервисами через хорошо определённые интерфейсы (обычно через API).
Представьте приложение для электронной коммерции, где интерфейс, управление пользователями, обработка заказов и управление продуктами — всё это часть одного большого приложения.
В приложении для электронной коммерции можно выделить отдельные микросервисы для управления пользователями, обработки заказов, управления продуктами и т.д. Эти микросервисы взаимодействуют друг с другом через API.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Идущий к IT
Твое резюме на HeadHunter — ОК, если ты видишь это.
HeadHunter сравнивает ключевые навыки в твоем резюме и в вакансии и в момент отклика отображает, насколько % ты соответствуешь требованиям.
Специальный бейджик «Подходит по навыкам на 100%» отображается, если соответствие составляет более 60%.
Если при просмотре вакансий ты видишь такой бейджик, это значит, что список навыков в твоем резюме качественно составлен.
Это важный параметр, так как рекрутерам чаще показываются резюме с лучшим соответствием.
О том, как правильно указывать ключевые навыки и оптимизировать свое резюме я уже рассказывал в этом видео
HeadHunter сравнивает ключевые навыки в твоем резюме и в вакансии и в момент отклика отображает, насколько % ты соответствуешь требованиям.
Специальный бейджик «Подходит по навыкам на 100%» отображается, если соответствие составляет более 60%.
Если при просмотре вакансий ты видишь такой бейджик, это значит, что список навыков в твоем резюме качественно составлен.
Это важный параметр, так как рекрутерам чаще показываются резюме с лучшим соответствием.
О том, как правильно указывать ключевые навыки и оптимизировать свое резюме я уже рассказывал в этом видео
Мапы (карты) не являются потокобезопасными по умолчанию, что означает, что одновременное чтение и запись в мапу из нескольких горутин может привести к состояниям гонки, некорректным данным и паникам. Давайте рассмотрим, как можно обеспечить потокобезопасность при работе с мапами.
Если несколько горутин одновременно пытаются читать и изменять мапу, возникает конкуренция за доступ к данным, что приводит к непредсказуемому поведению программы. Это может включать утечки данных, несогласованность данных и даже крах программы.
Один из наиболее простых и распространенных способов обеспечить потокобезопасность — использование мьютексов (
sync.Mutex
). В этом примере мьютекс используется для защиты операций записи и чтения, обеспечивая эксклюзивный доступ к мапе.package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
m := make(map[string]int)
var wg sync.WaitGroup
write := func(key string, value int) {
mu.Lock()
m[key] = value
mu.Unlock()
}
read := func(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}
wg.Add(2)
go func() {
defer wg.Done()
write("key1", 42)
}()
go func() {
defer wg.Done()
fmt.Println(read("key1"))
}()
wg.Wait()
}
Если ожидается частое чтение и редкие записи, можно использовать
sync.RWMutex
, который позволяет множественное чтение, но блокирует доступ на запись. Здесь RWMutex
позволяет параллельное чтение, но блокирует все операции на запись, что улучшает производительность при частых чтениях.package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.RWMutex
m := make(map[string]int)
var wg sync.WaitGroup
write := func(key string, value int) {
mu.Lock()
m[key] = value
mu.Unlock()
}
read := func(key string) int {
mu.RLock()
defer mu.RUnlock()
return m[key]
}
wg.Add(2)
go func() {
defer wg.Done()
write("key1", 42)
}()
go func() {
defer wg.Done()
fmt.Println(read("key1"))
}()
wg.Wait()
}
Go предоставляет пакет
sync.Map
, который изначально создан для конкурентного использования. sync.Map
автоматически обеспечивает потокобезопасность для операций чтения, записи и удаления.package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
m.Store("key1", 42)
}()
go func() {
defer wg.Done()
value, _ := m.Load("key1")
fmt.Println(value)
}()
wg.Wait()
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Проектировался с учетом простоты и производительности, и одно из решений разработчиков — не делать мапы (карты) потокобезопасными по умолчанию.
Потокобезопасность требует дополнительных накладных расходов для синхронизации доступа к данным. Если бы мапы в Go были потокобезопасными по умолчанию, каждый доступ к мапе был бы медленнее из-за необходимости блокировок. Большинство операций с мапами в реальных приложениях не требуют потокобезопасности, так как часто они выполняются в одном потоке или используются мапы, которые не модифицируются одновременно.
Go следует философии явного управления, где программисты сами должны решать, когда и как использовать синхронизацию. Это позволяет более точно контролировать производительность и обеспечивает гибкость при проектировании многопоточных приложений.
Потокобезопасные структуры данных, такие как
sync.Map
, могут быть сложными для понимания и использования. Введение потокобезопасности по умолчанию усложнило бы стандартные мапы, делая их менее интуитивными и простыми для использования.Эти мьютексы позволяют программистам вручную управлять синхронизацией доступа к мапам.
sync.Mutex
используется для блокировки доступа при чтении и записи, а sync.RWMutex
позволяет параллельное чтение и эксклюзивную запись.var mu sync.Mutex
m := make(map[string]int)
// запись с блокировкой
mu.Lock()
m["key"] = 42
mu.Unlock()
// чтение с блокировкой
mu.Lock()
value := m["key"]
mu.Unlock()
Специальная структура данных, которая изначально спроектирована для безопасного использования в многопоточной среде.
sync.Map
оптимизирован для сценариев с частыми чтениями и редкими записями.var m sync.Map
m.Store("key", 42)
value, ok := m.Load("key")
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Каналы изначально проектировались как потокобезопасные средства для коммуникации между горутинами. Однако, несмотря на их встроенную потокобезопасность, есть определенные правила и ограничения, которые необходимо учитывать для корректного использования каналов.
Каналы безопасны для использования из нескольких горутин. Это означает, что вы можете отправлять данные в канал из одной горутины и получать данные из другой горутины без дополнительной синхронизации.
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
ch <- 42
}()
go func() {
defer wg.Done()
value := <-ch
fmt.Println(value)
}()
wg.Wait()
}
Несколько горутин могут безопасно отправлять данные в один и тот же канал.
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int, 2)
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
ch <- 1
}()
go func() {
defer wg.Done()
ch <- 2
}()
go func() {
defer wg.Done()
for i := 0; i < 2; i++ {
fmt.Println(<-ch)
}
}()
wg.Wait()
}
Несколько горутин могут безопасно получать данные из одного и того же канала.
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int, 2)
var wg sync.WaitGroup
ch <- 1
ch <- 2
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println(<-ch)
}()
go func() {
defer wg.Done()
fmt.Println(<-ch)
}()
wg.Wait()
}
Канал должен закрываться только одной горутиной, и закрытие канала из нескольких горутин одновременно приведет к панике.
package main
func main() {
ch := make(chan int)
go func() {
close(ch) // Это допустимо
}()
go func() {
close(ch) // Это приведет к панике
}()
}
Попытка отправки данных в закрытый канал также вызовет панику. Поэтому, перед отправкой данных необходимо убедиться, что канал не закрыт.
package main
func main() {
ch := make(chan int)
close(ch)
ch <- 42 // Это приведет к панике
}
Если одна горутина закрывает канал, другая горутина может получить нулевое значение при чтении из него, что может привести к неправильным выводам, если это не предусмотрено.
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
close(ch)
}()
value, ok := <-ch
if !ok {
fmt.Println("Канал закрыт")
} else {
fmt.Println(value)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Использование слайсов в многопоточном (concurrent) контексте в Go требует особого внимания к потокобезопасности. По умолчанию слайсы не являются потокобезопасными, что означает, что одновременное чтение и запись в слайс из нескольких горутин может привести к состояниям гонки, некорректным данным и паникам. Давайте рассмотрим, насколько безопасно использовать слайсы в условиях concurrency и какие подходы позволяют обеспечить безопасность.
Если несколько горутин одновременно изменяют содержимое слайса или его длину, это может привести к состояниям гонки и повреждению данных.
При добавлении элементов в слайс его размер может увеличиваться, что может привести к перераспределению памяти и копированию содержимого, создавая потенциальные проблемы при одновременном доступе.
Если слайс используется только для чтения, его можно безопасно использовать из нескольких горутин.
package main
import (
"fmt"
"sync"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
for _, v := range slice {
wg.Add(1)
go func(val int) {
defer wg.Done()
fmt.Println(val)
}(v)
}
wg.Wait()
}
Если каждая горутина должна иметь независимую копию слайса, передавайте копию в каждую горутину.
package main
import (
"fmt"
"sync"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
for _, v := range slice {
wg.Add(1)
go func(val int) {
defer wg.Done()
fmt.Println(val)
}(v)
}
wg.Wait()
}
Для обеспечения безопасного одновременного чтения и записи используйте механизмы синхронизации, такие как мьютексы.
package main
import (
"fmt"
"sync"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
for _, v := range slice {
wg.Add(1)
go func(val int) {
defer wg.Done()
fmt.Println(val)
}(v)
}
wg.Wait()
}
В некоторых случаях лучше использовать структуры данных, которые изначально проектированы для потокобезопасного доступа, такие как
sync.Map
для мап или специальные библиотеки для потокобезопасных коллекций.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM