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

Что такое слабая связность?

1.8 Middle🔥 161 комментариев
#SOLID и паттерны проектирования

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Слабая связность

Слабая связность (Low Coupling или Loose Coupling) — это принцип архитектуры и проектирования, при котором компоненты приложения минимально зависят друг от друга. Компоненты независимы, взаимодействуют через четко определенные интерфейсы, и изменения в одном компоненте не вызывают изменения в других.

Связность vs Слабая связность

Высокая связность (Tight Coupling) — ❌ Плохо

// ❌ Класс NotificationService напрямую создает EmailService
public class NotificationService {
    private EmailService emailService = new EmailService();
    private SMSService smsService = new SMSService();
    
    public void notifyUser(String message) {
        emailService.send(message);
        smsService.send(message);
    }
}

public class EmailService {
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

Проблемы:

  • NotificationService жёстко привязан к EmailService и SMSService
  • Изменение EmailService потребует изменения NotificationService
  • Сложно тестировать (нельзя использовать мок-объекты)
  • Сложно добавить новый способ уведомления (push-уведомления)

Слабая связность (Loose Coupling) — ✅ Хорошо

// ✅ Используем интерфейс
public interface Notifier {
    void send(String message);
}

public class EmailService implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

public class SMSService implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

public class NotificationService {
    private List<Notifier> notifiers;
    
    // Внедрение зависимостей через конструктор
    public NotificationService(List<Notifier> notifiers) {
        this.notifiers = notifiers;
    }
    
    public void notifyUser(String message) {
        notifiers.forEach(notifier -> notifier.send(message));
    }
}

Преимущества:

  • NotificationService не зависит от конкретной реализации
  • Легко добавить новый Notifier без изменения NotificationService
  • Легко тестировать с использованием мок-объектов
  • Гибкость и расширяемость

Практические примеры слабой связности

1. Dependency Injection (внедрение зависимостей)

public class UserService {
    private UserRepository userRepository;
    private EmailService emailService;
    
    // ✅ Зависимости внедряются в конструктор
    public UserService(UserRepository userRepository, 
                       EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    public void registerUser(String email, String password) {
        User user = new User(email, password);
        userRepository.save(user);
        emailService.sendVerificationEmail(email);
    }
}

// Интерфейсы для гибкости
public interface UserRepository {
    void save(User user);
    User findByEmail(String email);
}

public class DatabaseUserRepository implements UserRepository {
    @Override
    public void save(User user) {
        // Сохранение в БД
    }
    
    @Override
    public User findByEmail(String email) {
        // Запрос в БД
        return null;
    }
}

2. Тестирование с использованием Mocks

import org.mockito.Mock;
import org.mockito.InjectMocks;
import static org.mockito.Mockito.*;

public class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    
    @Mock
    private EmailService emailService;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    public void testRegisterUser() {
        // Arrange
        String email = "user@example.com";
        String password = "password";
        
        // Act
        userService.registerUser(email, password);
        
        // Assert
        verify(userRepository).save(any(User.class));
        verify(emailService).sendVerificationEmail(email);
    }
}

3. Event-Driven Architecture

// ✅ События вместо прямых вызовов
public class UserRegisteredEvent {
    private String email;
    
    public UserRegisteredEvent(String email) {
        this.email = email;
    }
    
    public String getEmail() {
        return email;
    }
}

public class UserService {
    private EventPublisher eventPublisher;
    private UserRepository userRepository;
    
    public UserService(EventPublisher eventPublisher, 
                       UserRepository userRepository) {
        this.eventPublisher = eventPublisher;
        this.userRepository = userRepository;
    }
    
    public void registerUser(String email, String password) {
        User user = new User(email, password);
        userRepository.save(user);
        
        // Публикуем событие вместо прямого вызова сервиса
        eventPublisher.publish(new UserRegisteredEvent(email));
    }
}

// Email сервис слушает события
public class EmailNotificationListener {
    private EmailService emailService;
    
    public EmailNotificationListener(EmailService emailService) {
        this.emailService = emailService;
    }
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        emailService.sendVerificationEmail(event.getEmail());
    }
}

4. Стратегия и Factory Pattern

// ✅ Выбор реализации во время выполнения
public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Оплата кредитной картой: " + amount);
    }
}

public class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Оплата через PayPal: " + amount);
    }
}

public class PaymentProcessor {
    private PaymentStrategy strategy;
    
    public PaymentProcessor(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void processPayment(double amount) {
        strategy.pay(amount);
    }
}

// Использование
PaymentStrategy creditCard = new CreditCardPayment();
PaymentProcessor processor = new PaymentProcessor(creditCard);
processor.processPayment(100.0); // Легко переключиться на PayPal

5. Spring Dependency Injection

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final EmailService emailService;
    
    // ✅ Spring автоматически внедрит зависимости
    @Autowired
    public OrderService(OrderRepository orderRepository, 
                        EmailService emailService) {
        this.orderRepository = orderRepository;
        this.emailService = emailService;
    }
    
    public void createOrder(Order order) {
        orderRepository.save(order);
        emailService.sendOrderConfirmation(order.getCustomerEmail());
    }
}

@Repository
public class DatabaseOrderRepository implements OrderRepository {
    // Реализация
}

// В другом месте можно подставить другую реализацию
@Configuration
public class RepositoryConfig {
    @Bean
    public OrderRepository orderRepository() {
        return new CacheOrderRepository(); // Или другая реализация
    }
}

Достижение слабой связности

1. Используй интерфейсы и абстрактные классы:

public interface Logger {
    void log(String message);
}

public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

2. Внедри зависимости:

public class Application {
    private Logger logger;
    
    public Application(Logger logger) {
        this.logger = logger;
    }
}

3. Используй Factory patterns:

public class LoggerFactory {
    public static Logger createLogger(String type) {
        if ("console".equals(type)) {
            return new ConsoleLogger();
        }
        return new FileLogger();
    }
}

4. Используй Dependency Injection контейнеры (Spring, Guice):

@Component
public class MyService {
    @Autowired
    private Logger logger;
}

Плюсы слабой связности

  • Тестируемость: Легко создавать mock-объекты
  • Гибкость: Легко менять реализации
  • Поддерживаемость: Изменения не влияют на другие компоненты
  • Переиспользуемость: Компоненты независимы
  • Масштабируемость: Легче расширять систему

Минусы слабой связности

  • Сложность: Может быть больше кода и интерфейсов
  • Производительность: Некоторые абстракции добавляют overhead
  • Переусложнение: Не всегда нужна для простых приложений

Слабая связность — фундаментальный принцип SOLID и чистой архитектуры, критически важный для разработки гибких и поддерживаемых систем.

Что такое слабая связность? | PrepBro