← Назад к вопросам

Какие принципы SOLID применяешь при разработке?

2.0 Middle🔥 241 комментариев
#Микросервисы и архитектура

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принципы SOLID в Go-разработке

Применение SOLID принципов в Go имеет свою специфику из-за особенностей языка (отсутствие классического ООП с наследованием, наличие интерфейсов, структур и композиции). Вот как я применяю эти принципы на практике:

1. Single Responsibility Principle (Принцип единственной ответственности)

В Go этот принцип реализуется через создание небольших, сфокусированных типов и функций. Каждая структура и каждый метод должны решать одну задачу.

// ПЛОХО: Структура занимается и валидацией, и сохранением, и отправкой email
type UserService struct {
    db *sql.DB
    mailer *Mailer
}

func (s *UserService) ProcessUser(data UserData) error {
    // Валидация
    if data.Email == "" {
        return errors.New("email required")
    }
    
    // Сохранение в БД
    _, err := s.db.Exec("INSERT INTO users...")
    
    // Отправка email
    s.mailer.SendWelcomeEmail(data.Email)
    
    return err
}

// ХОРОШО: Разделение ответственности
type UserValidator struct{}
func (v *UserValidator) Validate(data UserData) error { /* ... */ }

type UserRepository struct {
    db *sql.DB
}
func (r *UserRepository) Save(data UserData) error { /* ... */ }

type UserNotifier struct {
    mailer *Mailer
}
func (n *UserNotifier) SendWelcome(email string) error { /* ... */ }

2. Open-Closed Principle (Принцип открытости/закрытости)

В Go достигается через интерфейсы и композицию. Сущности должны быть открыты для расширения, но закрыты для модификации.

// Базовый интерфейс
type PaymentProcessor interface {
    Process(amount float64) error
}

// Конкретные реализации
type CreditCardProcessor struct{}
func (p *CreditCardProcessor) Process(amount float64) error {
    return processCreditCard(amount)
}

type PayPalProcessor struct{}
func (p *PayPalProcessor) Process(amount float64) error {
    return processPayPal(amount)
}

// Система работает с интерфейсом, а не с конкретными типами
type PaymentService struct {
    processor PaymentProcessor
}

func (s *PaymentService) MakePayment(amount float64) error {
    return s.processor.Process(amount)
}

// Добавление нового способа оплаты не требует изменения существующего кода
type CryptoProcessor struct{}
func (p *CryptoProcessor) Process(amount float64) error {
    return processCrypto(amount)
}

3. Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

В контексте Go это означает, что типы, реализующие интерфейс, должны быть взаимозаменяемы без изменения корректности программы.

type Reader interface {
    Read(p []byte) (n int, err error)
}

// Разные реализации должны корректно работать везде, где ожидается Reader
type FileReader struct{ /* ... */ }
func (r *FileReader) Read(p []byte) (int, error) { /* ... */ }

type BufferReader struct{ /* ... */ }
func (r *BufferReader) Read(p []byte) (int, error) { /* ... */ }

// Функция работает с любым Reader
func ProcessStream(r Reader) error {
    buffer := make([]byte, 1024)
    _, err := r.Read(buffer)
    return err
}

4. Interface Segregation Principle (Принцип разделения интерфейсов)

Создавайте небольшие, специфичные интерфейсы вместо больших "монолитных". В Go это особенно важно из-за неявной реализации интерфейсов.

// ПЛОХО: Один большой интерфейс
type MonsterInterface interface {
    Walk(distance int)
    Swim(distance int)
    Fly(distance int)
    Shoot()
    Talk()
}

// ХОРОШО: Разделенные интерфейсы
type Walker interface {
    Walk(distance int)
}

type Swimmer interface {
    Swim(distance int)
}

type Flyer interface {
    Fly(distance int)
}

type Shooter interface {
    Shoot()
}

// Структуры реализуют только нужные интерфейсы
type Human struct{}
func (h *Human) Walk(distance int) { /* ... */ }

type Duck struct{}
func (d *Duck) Walk(distance int) { /* ... */ }
func (d *Duck) Swim(distance int) { /* ... */ }
func (d *Duck) Fly(distance int) { /* ... */ }

5. Dependency Inversion Principle (Принцип инверсии зависимостей)

Внедрение зависимостей через интерфейсы, а не конкретные реализации. В Go часто используется внедрение зависимостей через конструктор.

// Высокоуровневый модуль зависит от абстракций
type UserService struct {
    repository UserRepository  // Интерфейс, не конкретная реализация
    logger     Logger          // Интерфейс
}

// Определение интерфейсов на правильном уровне абстракции
type UserRepository interface {
    FindByID(id string) (*User, error)
    Save(user *User) error
}

type Logger interface {
    Info(msg string)
    Error(msg string)
}

// Конструктор принимает зависимости
func NewUserService(repo UserRepository, log Logger) *UserService {
    return &UserService{
        repository: repo,
        logger:     log,
    }
}

// Использование
func main() {
    // Конкретные реализации
    repo := NewPostgresUserRepository()
    logger := NewZapLogger()
    
    // Внедрение зависимостей
    service := NewUserService(repo, logger)
}

Практическое применение в Go-проектах

В своих проектах я применяю SOLID следующим образом:

  • SRP: Дроблю большие методы на маленькие, разбиваю монолитные структуры на специализированные
  • OCP: Использую интерфейсы для расширения функциональности через новые реализации
  • LSP: Слежу, чтобы реализации интерфейсов были семантически корректными
  • ISP: Создаю интерфейсы в месте их использования (как в стандартной библиотеке: io.Reader, io.Writer)
  • DIP: Внедряю зависимости через интерфейсы, использую DI-контейнеры в крупных проектах

Особенности Go

В Go SOLID применяется с учетом идиом языка:

  • Композиция вместо наследования
  • Маленькие интерфейсы (1-2 метода)
  • Duck typing через интерфейсы
  • Принцип "accept interfaces, return structs"

Эти принципы помогают создавать тестируемый, поддерживаемый и масштабируемый код, даже в условиях быстрой разработки, характерной для Go-проектов.

Какие принципы SOLID применяешь при разработке? | PrepBro