Files
Alexander Abakumov 15e744da49 Initial commit: Complete Software Architecture course with 11 modules
- Added comprehensive course structure with 11 modules
- Included theoretical content, practical examples, and tasks
- Multi-language code examples (Java, Rust, Node.js, Python)
- Detailed analysis of architectural approaches and trade-offs
- Complete caching strategies documentation
- Architecture examples from real-world systems (Twitter, Netflix, Uber)
- Structured navigation and learning path
- Ready for educational use
2025-09-09 22:08:07 +03:00

31 KiB

Принципы проектирования

Навигация


SOLID принципы

1. Single Responsibility Principle (SRP)

Принцип единственной ответственности

Класс должен иметь только одну причину для изменения

Пример нарушения:

// Нарушение SRP - класс делает слишком много
class UserManager {
    private UserRepository userRepository;
    private EmailService emailService;
    private PasswordEncoder passwordEncoder;
    private AuditService auditService;
    
    public User createUser(CreateUserRequest request) {
        // 1. Валидация данных
        if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
            throw new ValidationException("Invalid email");
        }
        
        // 2. Хеширование пароля
        String hashedPassword = passwordEncoder.encode(request.getPassword());
        
        // 3. Создание пользователя
        User user = new User(request.getName(), request.getEmail(), hashedPassword);
        user = userRepository.save(user);
        
        // 4. Отправка приветственного email
        emailService.sendWelcomeEmail(user.getEmail());
        
        // 5. Аудит
        auditService.logUserCreation(user.getId());
        
        return user;
    }
    
    public void updateUser(Long userId, UpdateUserRequest request) {
        // Аналогично много ответственностей...
    }
    
    public void deleteUser(Long userId) {
        // Аналогично много ответственностей...
    }
}

Правильная реализация:

// Разделение ответственностей
class User {
    private Long id;
    private String name;
    private String email;
    private String passwordHash;
    
    // Только данные пользователя и бизнес-логика
    public void changePassword(String newPassword, PasswordEncoder encoder) {
        this.passwordHash = encoder.encode(newPassword);
    }
    
    public boolean isEmailValid() {
        return email != null && email.contains("@");
    }
}

class UserValidator {
    public void validateCreateRequest(CreateUserRequest request) {
        if (request.getName() == null || request.getName().trim().isEmpty()) {
            throw new ValidationException("Name is required");
        }
        if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
            throw new ValidationException("Invalid email");
        }
        if (request.getPassword() == null || request.getPassword().length() < 8) {
            throw new ValidationException("Password must be at least 8 characters");
        }
    }
    
    private boolean isValidEmail(String email) {
        return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
}

class UserRepository {
    public User save(User user) {
        // Только операции с БД
        return userRepository.save(user);
    }
    
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }
}

class UserNotificationService {
    public void sendWelcomeEmail(String email) {
        // Только отправка уведомлений
        emailService.sendWelcomeEmail(email);
    }
}

class UserAuditService {
    public void logUserCreation(Long userId) {
        // Только аудит
        auditService.logUserCreation(userId);
    }
}

// Координатор, который использует все сервисы
class UserService {
    private UserValidator validator;
    private UserRepository repository;
    private UserNotificationService notificationService;
    private UserAuditService auditService;
    private PasswordEncoder passwordEncoder;
    
    public User createUser(CreateUserRequest request) {
        // 1. Валидация
        validator.validateCreateRequest(request);
        
        // 2. Создание пользователя
        String hashedPassword = passwordEncoder.encode(request.getPassword());
        User user = new User(request.getName(), request.getEmail(), hashedPassword);
        
        // 3. Сохранение
        user = repository.save(user);
        
        // 4. Уведомления
        notificationService.sendWelcomeEmail(user.getEmail());
        
        // 5. Аудит
        auditService.logUserCreation(user.getId());
        
        return user;
    }
}

2. Open/Closed Principle (OCP)

Принцип открытости/закрытости

Программные сущности должны быть открыты для расширения, но закрыты для модификации

Пример нарушения:

// Нарушение OCP - нужно модифицировать класс для добавления новых типов
class OrderProcessor {
    public void processOrder(Order order) {
        switch (order.getPaymentType()) {
            case CREDIT_CARD:
                processCreditCardPayment(order);
                break;
            case PAYPAL:
                processPayPalPayment(order);
                break;
            case BANK_TRANSFER:
                processBankTransferPayment(order);
                break;
            // При добавлении нового типа платежа нужно модифицировать этот класс
        }
    }
    
    private void processCreditCardPayment(Order order) {
        // логика обработки кредитной карты
    }
    
    private void processPayPalPayment(Order order) {
        // логика обработки PayPal
    }
    
    private void processBankTransferPayment(Order order) {
        // логика обработки банковского перевода
    }
}

Правильная реализация:

// Соблюдение OCP - открыто для расширения, закрыто для модификации
interface PaymentProcessor {
    boolean canProcess(PaymentType paymentType);
    PaymentResult processPayment(PaymentRequest request);
}

class CreditCardProcessor implements PaymentProcessor {
    @Override
    public boolean canProcess(PaymentType paymentType) {
        return paymentType == PaymentType.CREDIT_CARD;
    }
    
    @Override
    public PaymentResult processPayment(PaymentRequest request) {
        // логика обработки кредитной карты
        return new PaymentResult(true, "Payment processed via credit card");
    }
}

class PayPalProcessor implements PaymentProcessor {
    @Override
    public boolean canProcess(PaymentType paymentType) {
        return paymentType == PaymentType.PAYPAL;
    }
    
    @Override
    public PaymentResult processPayment(PaymentRequest request) {
        // логика обработки PayPal
        return new PaymentResult(true, "Payment processed via PayPal");
    }
}

class CryptocurrencyProcessor implements PaymentProcessor {
    @Override
    public boolean canProcess(PaymentType paymentType) {
        return paymentType == PaymentType.CRYPTOCURRENCY;
    }
    
    @Override
    public PaymentResult processPayment(PaymentRequest request) {
        // логика обработки криптовалюты
        return new PaymentResult(true, "Payment processed via cryptocurrency");
    }
}

// Основной класс не нужно модифицировать для добавления новых процессоров
class OrderProcessor {
    private List<PaymentProcessor> processors;
    
    public OrderProcessor(List<PaymentProcessor> processors) {
        this.processors = processors;
    }
    
    public PaymentResult processOrder(Order order) {
        PaymentProcessor processor = processors.stream()
            .filter(p -> p.canProcess(order.getPaymentType()))
            .findFirst()
            .orElseThrow(() -> new UnsupportedPaymentTypeException(
                "No processor found for payment type: " + order.getPaymentType()));
        
        PaymentRequest request = new PaymentRequest(order.getAmount(), order.getPaymentDetails());
        return processor.processPayment(request);
    }
}

// Конфигурация процессоров
@Configuration
public class PaymentProcessorConfig {
    
    @Bean
    public List<PaymentProcessor> paymentProcessors() {
        return Arrays.asList(
            new CreditCardProcessor(),
            new PayPalProcessor(),
            new CryptocurrencyProcessor() // Новый процессор добавляется без модификации существующего кода
        );
    }
}

3. Liskov Substitution Principle (LSP)

Принцип подстановки Лисков

Объекты производного класса должны быть заменяемы объектами базового класса

Пример нарушения:

// Нарушение LSP
class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // Нарушение LSP - изменяет поведение
    }
    
    @Override
    public void setHeight(int height) {
        this.height = height;
        this.width = height; // Нарушение LSP - изменяет поведение
    }
}

// Проблема: код, работающий с Rectangle, может сломаться с Square
public void testRectangle(Rectangle rectangle) {
    rectangle.setWidth(5);
    rectangle.setHeight(4);
    assert rectangle.getArea() == 20; // Для Square это будет 16!
}

Правильная реализация:

// Соблюдение LSP
abstract class Shape {
    public abstract int getArea();
}

class Rectangle extends Shape {
    private int width;
    private int height;
    
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    @Override
    public int getArea() {
        return width * height;
    }
}

class Square extends Shape {
    private int side;
    
    public Square(int side) {
        this.side = side;
    }
    
    public void setSide(int side) {
        this.side = side;
    }
    
    @Override
    public int getArea() {
        return side * side;
    }
}

// Теперь любой Shape можно заменить другим Shape
public void testShape(Shape shape) {
    int area = shape.getArea();
    // Логика работает корректно для любого типа Shape
}

4. Interface Segregation Principle (ISP)

Принцип разделения интерфейсов

Клиенты не должны зависеть от интерфейсов, которые они не используют

Пример нарушения:

// Нарушение ISP - слишком большой интерфейс
interface Worker {
    void work();
    void eat();
    void sleep();
    void code();
    void design();
    void test();
    void deploy();
}

class Developer implements Worker {
    @Override
    public void work() { /* разработка */ }
    
    @Override
    public void eat() { /* еда */ }
    
    @Override
    public void sleep() { /* сон */ }
    
    @Override
    public void code() { /* кодирование */ }
    
    @Override
    public void design() { /* не используется */ }
    
    @Override
    public void test() { /* не используется */ }
    
    @Override
    public void deploy() { /* не используется */ }
}

class Designer implements Worker {
    @Override
    public void work() { /* работа */ }
    
    @Override
    public void eat() { /* еда */ }
    
    @Override
    public void sleep() { /* сон */ }
    
    @Override
    public void code() { /* не используется */ }
    
    @Override
    public void design() { /* дизайн */ }
    
    @Override
    public void test() { /* не используется */ }
    
    @Override
    public void deploy() { /* не используется */ }
}

Правильная реализация:

// Соблюдение ISP - разделение интерфейсов
interface Human {
    void eat();
    void sleep();
}

interface Worker extends Human {
    void work();
}

interface Developer extends Worker {
    void code();
}

interface Designer extends Worker {
    void design();
}

interface Tester extends Worker {
    void test();
}

interface DevOps extends Worker {
    void deploy();
}

// Теперь каждый класс реализует только нужные ему интерфейсы
class JavaDeveloper implements Developer {
    @Override
    public void eat() { /* еда */ }
    
    @Override
    public void sleep() { /* сон */ }
    
    @Override
    public void work() { /* работа */ }
    
    @Override
    public void code() { /* Java кодирование */ }
}

class UIDesigner implements Designer {
    @Override
    public void eat() { /* еда */ }
    
    @Override
    public void sleep() { /* сон */ }
    
    @Override
    public void work() { /* работа */ }
    
    @Override
    public void design() { /* UI дизайн */ }
}

// Можно комбинировать интерфейсы
class FullStackDeveloper implements Developer, Designer {
    @Override
    public void eat() { /* еда */ }
    
    @Override
    public void sleep() { /* сон */ }
    
    @Override
    public void work() { /* работа */ }
    
    @Override
    public void code() { /* кодирование */ }
    
    @Override
    public void design() { /* дизайн */ }
}

5. Dependency Inversion Principle (DIP)

Принцип инверсии зависимостей

Модули высокого уровня не должны зависеть от модулей низкого уровня

Пример нарушения:

// Нарушение DIP - высокоуровневый модуль зависит от низкоуровневого
class EmailService {
    public void sendEmail(String to, String subject, String body) {
        // Прямая зависимость от конкретной реализации
        SMTPClient smtpClient = new SMTPClient();
        smtpClient.connect("smtp.gmail.com", 587);
        smtpClient.send(to, subject, body);
        smtpClient.disconnect();
    }
}

class OrderService {
    private EmailService emailService;
    
    public OrderService() {
        this.emailService = new EmailService(); // Жесткая зависимость
    }
    
    public void processOrder(Order order) {
        // Обработка заказа
        order.setStatus(OrderStatus.PROCESSED);
        
        // Отправка уведомления
        emailService.sendEmail(
            order.getCustomerEmail(),
            "Order Confirmation",
            "Your order has been processed"
        );
    }
}

Правильная реализация:

// Соблюдение DIP - зависимость от абстракций
interface NotificationService {
    void sendNotification(String recipient, String subject, String message);
}

interface EmailProvider {
    void sendEmail(String to, String subject, String body);
}

class SMTPEmailProvider implements EmailProvider {
    @Override
    public void sendEmail(String to, String subject, String body) {
        SMTPClient smtpClient = new SMTPClient();
        smtpClient.connect("smtp.gmail.com", 587);
        smtpClient.send(to, subject, body);
        smtpClient.disconnect();
    }
}

class SendGridEmailProvider implements EmailProvider {
    @Override
    public void sendEmail(String to, String subject, String body) {
        SendGridClient client = new SendGridClient();
        client.send(to, subject, body);
    }
}

class EmailNotificationService implements NotificationService {
    private EmailProvider emailProvider;
    
    public EmailNotificationService(EmailProvider emailProvider) {
        this.emailProvider = emailProvider; // Инъекция зависимости
    }
    
    @Override
    public void sendNotification(String recipient, String subject, String message) {
        emailProvider.sendEmail(recipient, subject, message);
    }
}

class SMSNotificationService implements NotificationService {
    private SMSProvider smsProvider;
    
    public SMSNotificationService(SMSProvider smsProvider) {
        this.smsProvider = smsProvider;
    }
    
    @Override
    public void sendNotification(String recipient, String subject, String message) {
        smsProvider.sendSMS(recipient, message);
    }
}

// Высокоуровневый модуль зависит от абстракции
class OrderService {
    private NotificationService notificationService;
    
    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService; // Инъекция зависимости
    }
    
    public void processOrder(Order order) {
        // Обработка заказа
        order.setStatus(OrderStatus.PROCESSED);
        
        // Отправка уведомления через абстракцию
        notificationService.sendNotification(
            order.getCustomerEmail(),
            "Order Confirmation",
            "Your order has been processed"
        );
    }
}

// Конфигурация зависимостей
@Configuration
public class AppConfig {
    
    @Bean
    public EmailProvider emailProvider() {
        return new SMTPEmailProvider(); // Можно легко заменить на SendGridEmailProvider
    }
    
    @Bean
    public NotificationService notificationService(EmailProvider emailProvider) {
        return new EmailNotificationService(emailProvider);
    }
    
    @Bean
    public OrderService orderService(NotificationService notificationService) {
        return new OrderService(notificationService);
    }
}

Дополнительные принципы

DRY (Не повторяйся)

Не повторяйся

  • Избегайте дублирования кода
  • Выносите общую логику в отдельные функции/классы
  • Используйте абстракции для переиспользования

Пример нарушения:

// Нарушение DRY - дублирование кода
class UserService {
    public void createUser(String email, String password) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email cannot be null or empty");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
        // создание пользователя
    }
    
    public void updateUser(String email, String password) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email cannot be null or empty");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
        // обновление пользователя
    }
}

Правильная реализация:

// Соблюдение DRY - выделение общей логики
class ValidationUtils {
    public static void validateEmail(String email) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email cannot be null or empty");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
    }
    
    public static void validatePassword(String password) {
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
    }
}

class UserService {
    public void createUser(String email, String password) {
        ValidationUtils.validateEmail(email);
        ValidationUtils.validatePassword(password);
        // создание пользователя
    }
    
    public void updateUser(String email, String password) {
        ValidationUtils.validateEmail(email);
        ValidationUtils.validatePassword(password);
        // обновление пользователя
    }
}

KISS (Будь проще)

Делай проще

  • Простые решения часто лучше сложных
  • Избегайте преждевременной оптимизации
  • Читаемость кода важнее "умности"

Пример нарушения:

// Нарушение KISS - излишняя сложность
class ComplexCalculator {
    public double calculate(String expression) {
        if (expression == null || expression.trim().isEmpty()) {
            throw new IllegalArgumentException("Expression cannot be null or empty");
        }
        
        String trimmedExpression = expression.trim();
        if (!trimmedExpression.matches("^[0-9+\\-*/\\s()]+$")) {
            throw new IllegalArgumentException("Invalid characters in expression");
        }
        
        // Сложная логика парсинга и вычисления...
        return evaluateComplexExpression(trimmedExpression);
    }
    
    private double evaluateComplexExpression(String expr) {
        // Очень сложная реализация
        return 0.0;
    }
}

Правильная реализация:

// Соблюдение KISS - простое решение
class SimpleCalculator {
    public double add(double a, double b) {
        return a + b;
    }
    
    public double subtract(double a, double b) {
        return a - b;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
    
    public double divide(double a, double b) {
        if (b == 0) {
            throw new IllegalArgumentException("Division by zero");
        }
        return a / b;
    }
}

// Использование
SimpleCalculator calc = new SimpleCalculator();
double result = calc.add(calc.multiply(2, 3), calc.divide(10, 2)); // 2*3 + 10/2 = 11

YAGNI (Вам это не понадобится)

Тебе это не понадобится

  • Не реализуйте функциональность "на будущее"
  • Добавляйте функции только когда они действительно нужны
  • Избегайте over-engineering

Пример нарушения:

// Нарушение YAGNI - преждевременная оптимизация
class UserRepository {
    private Map<String, User> cache = new ConcurrentHashMap<>();
    private List<User> users = new ArrayList<>();
    private UserIndexer indexer = new UserIndexer();
    private UserValidator validator = new UserValidator();
    private UserMetricsCollector metrics = new UserMetricsCollector();
    
    public void saveUser(User user) {
        // Валидация (может не понадобиться)
        validator.validate(user);
        
        // Индексация (может не понадобиться)
        indexer.index(user);
        
        // Кэширование (может не понадобиться)
        cache.put(user.getId(), user);
        
        // Сохранение
        users.add(user);
        
        // Метрики (может не понадобиться)
        metrics.recordSave(user);
    }
}

Правильная реализация:

// Соблюдение YAGNI - только необходимая функциональность
class UserRepository {
    private List<User> users = new ArrayList<>();
    
    public void saveUser(User user) {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }
        users.add(user);
    }
    
    public User findById(String id) {
        if (id == null) {
            throw new IllegalArgumentException("ID cannot be null");
        }
        
        return users.stream()
            .filter(u -> u.getId().equals(id))
            .findFirst()
            .orElse(null);
    }
}

// Когда понадобится кэширование - добавим
// Когда понадобится валидация - добавим
// Когда понадобится индексация - добавим

Принципы модульности

Слабая связанность (Loose Coupling)

  • Компоненты минимально зависят друг от друга
  • Изменения в одном компоненте не влияют на другие
  • Легче тестировать и поддерживать

Пример нарушения:

// Нарушение - тесная связанность
class OrderService {
    private MySQLDatabase database;
    private SMTPEmailService emailService;
    private FileLogger logger;
    
    public OrderService() {
        this.database = new MySQLDatabase("localhost", "orders_db");
        this.emailService = new SMTPEmailService("smtp.gmail.com", 587);
        this.logger = new FileLogger("/var/log/orders.log");
    }
    
    public void processOrder(Order order) {
        // Прямая зависимость от конкретных реализаций
        database.save(order);
        emailService.sendEmail(order.getCustomerEmail(), "Order processed");
        logger.log("Order processed: " + order.getId());
    }
}

Правильная реализация:

// Соблюдение - слабая связанность
interface OrderRepository {
    void save(Order order);
}

interface NotificationService {
    void sendNotification(String recipient, String message);
}

interface LoggingService {
    void log(String message);
}

class OrderService {
    private OrderRepository repository;
    private NotificationService notificationService;
    private LoggingService loggingService;
    
    public OrderService(OrderRepository repository, 
                       NotificationService notificationService,
                       LoggingService loggingService) {
        this.repository = repository;
        this.notificationService = notificationService;
        this.loggingService = loggingService;
    }
    
    public void processOrder(Order order) {
        repository.save(order);
        notificationService.sendNotification(
            order.getCustomerEmail(), 
            "Order processed"
        );
        loggingService.log("Order processed: " + order.getId());
    }
}

// Конкретные реализации
class MySQLOrderRepository implements OrderRepository {
    @Override
    public void save(Order order) {
        // MySQL специфичная логика
    }
}

class EmailNotificationService implements NotificationService {
    @Override
    public void sendNotification(String recipient, String message) {
        // Email специфичная логика
    }
}

class FileLoggingService implements LoggingService {
    @Override
    public void log(String message) {
        // File logging специфичная логика
    }
}

Высокая сплоченность (High Cohesion)

  • Элементы внутри модуля тесно связаны
  • Модуль имеет четко определенную ответственность
  • Все функции модуля работают для достижения одной цели

Пример нарушения:

// Нарушение - низкая сплоченность
class UtilityClass {
    public void sendEmail(String to, String subject, String body) {
        // Отправка email
    }
    
    public void calculateTax(double amount, double rate) {
        // Расчет налога
    }
    
    public void validatePassword(String password) {
        // Валидация пароля
    }
    
    public void formatDate(Date date, String format) {
        // Форматирование даты
    }
    
    public void compressFile(String filePath) {
        // Сжатие файла
    }
}

Правильная реализация:

// Соблюдение - высокая сплоченность
class EmailService {
    public void sendEmail(String to, String subject, String body) {
        validateEmail(to);
        String formattedBody = formatEmailBody(body);
        deliverEmail(to, subject, formattedBody);
    }
    
    private void validateEmail(String email) {
        // Валидация email адреса
    }
    
    private String formatEmailBody(String body) {
        // Форматирование тела письма
        return body;
    }
    
    private void deliverEmail(String to, String subject, String body) {
        // Доставка email
    }
}

class TaxCalculator {
    public double calculateTax(double amount, double rate) {
        validateAmount(amount);
        validateRate(rate);
        return amount * rate;
    }
    
    private void validateAmount(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
    }
    
    private void validateRate(double rate) {
        if (rate < 0 || rate > 1) {
            throw new IllegalArgumentException("Rate must be between 0 and 1");
        }
    }
}

class PasswordValidator {
    public boolean isValid(String password) {
        return hasMinimumLength(password) && 
               containsUppercase(password) && 
               containsLowercase(password) && 
               containsDigit(password);
    }
    
    private boolean hasMinimumLength(String password) {
        return password.length() >= 8;
    }
    
    private boolean containsUppercase(String password) {
        return password.chars().anyMatch(Character::isUpperCase);
    }
    
    private boolean containsLowercase(String password) {
        return password.chars().anyMatch(Character::isLowerCase);
    }
    
    private boolean containsDigit(String password) {
        return password.chars().anyMatch(Character::isDigit);
    }
}