- 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
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);
}
}