Arch Patterns
99 subscribers
1 photo
26 links
Канал о грамотном проектировании ПО. Анализируем архитектурные паттерны, рисуем схемы и пишем код. Без воды, только практика.

Наш сайт: arch-patterns.ru

#designpatterns #softwarearchitecture #backend #dev
Download Telegram
Channel created
Шаблон проектирования Singleton 🏗

Singleton — это шаблон проектирования, который:
- Гарантирует, что у класса есть только один экземпляр 🔒
- Предоставляет глобальную точку доступа к этому экземпляру 🌍

Когда использовать? 🤔

- Когда нужен только один экземпляр класса, доступный для всех клиентов
- Для управления общими ресурсами (например, подключение к БД, файловый менеджер)
- Когда нужно контролировать доступ к некоторому общему ресурсу

Пример на Python 🐍

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
print("🏗 Создание нового экземпляра Singleton")
cls._instance = super().__new__(cls)
else:
print("♻️ Возврат существующего экземпляра Singleton")
return cls._instance

def some_business_logic(self):
print("🚀 Выполнение бизнес-логики")

Использование
print("Создаем первый экземпляр:")
s1 = Singleton()
s1.some_business_logic()

print("\nПытаемся создать второй экземпляр:")
s2 = Singleton()
s2.some_business_logic()

print("\nПроверяем, что s1 и s2 - это один и тот же объект:")
print(s1 is s2) # Выведет: True


Пример на C# 🖥

using System;

public sealed class Singleton
{
private static Singleton _instance = null;
private static readonly object padlock = new object();

private Singleton()
{
Console.WriteLine("🏗 Создание нового экземпляра Singleton");
}

public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (padlock)
{
if (_instance == null)
{
_instance = new Singleton();
}
else
{
Console.WriteLine("♻️ Возврат существующего экземпляра Singleton");
}
}
}
else
{
Console.WriteLine("♻️ Возврат существующего экземпляра Singleton");
}
return _instance;
}
}

public void SomeBusinessLogic()
{
Console.WriteLine("🚀 Выполнение бизнес-логики");
}
}

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Создаем первый экземпляр:");
Singleton s1 = Singleton.Instance;
s1.SomeBusinessLogic();

Console.WriteLine("\nПытаемся создать второй экземпляр:");
Singleton s2 = Singleton.Instance;
s2.SomeBusinessLogic();

Console.WriteLine("\nПроверяем, что s1 и s2 - это один и тот же объект:");
Console.WriteLine(ReferenceEquals(s1, s2)); // Выведет: True
}
}



Обратите внимание на некоторые отличия между реализациями:

1. В Python мы используем метод __new__ для контроля создания экземпляра. 🐍
2. В C# мы используем свойство Instance и двойную проверку блокировки для потокобезопасности. 🔒

Основные минусы использования Singleton:
1. Нарушает принцип единственной ответственности 📚
2. Усложняет модульное тестирование 🧪
3. Создает скрытые зависимости 🕵️
4. Нарушает принцип инверсии зависимостей 🔄
5. Проблемы с многопоточностью 🧵
6. Сложности с наследованием 👪
7. Создает глобальное состояние 🌍
8. Нарушает принцип открытости/закрытости 🔒
9. Усложняет сериализацию 💾
10. Проблемы с временем жизни объекта ⏳


Оба примера демонстрируют основную идею Singleton: обеспечение единственного экземпляра класса и глобальной точки доступа к нему. 🌟
Пример на Python:

import json
import os

class ConfigManager:
_instance = None

def __new__(cls):
if cls._instance is None:
print("🏗 Создание нового экземпляра ConfigManager")
cls._instance = super().__new__(cls)
cls._instance.config = {}
cls._instance.load_config()
return cls._instance

def load_config(self):
config_path = 'config.json'
if os.path.exists(config_path):
with open(config_path, 'r') as f:
self.config = json.load(f)
else:
self.config = {
'database_url': 'localhost',
'port': 5432,
'debug_mode': False,
'log_level': 'INFO'
}
self.save_config()

def save_config(self):
with open('config.json', 'w') as f:
json.dump(self.config, f, indent=4)

def get(self, key):
return self.config.get(key)

def set(self, key, value):
self.config[key] = value
self.save_config()

# Пример использования
def main():
# В разных частях приложения
db_manager = DatabaseManager()
logger = Logger()
app = Application()

# Все они используют один и тот же экземпляр ConfigManager
print("\nНастройки базы данных:")
print(f"URL: {db_manager.get_db_url()}")
print(f"Port: {db_manager.get_db_port()}")

print("\nНастройки логгера:")
print(f"Log Level: {logger.get_log_level()}")

print("\nНастройки приложения:")
print(f"Debug Mode: {app.is_debug_mode()}")

# Изменение настройки
app.set_debug_mode(True)

print("\nПосле изменения:")
print(f"Debug Mode: {app.is_debug_mode()}")

class DatabaseManager:
def __init__(self):
self.config = ConfigManager()

def get_db_url(self):
return self.config.get('database_url')

def get_db_port(self):
return self.config.get('port')

class Logger:
def __init__(self):
self.config = ConfigManager()

def get_log_level(self):
return self.config.get('log_level')

class Application:
def __init__(self):
self.config = ConfigManager()

def is_debug_mode(self):
return self.config.get('debug_mode')

def set_debug_mode(self, value):
self.config.set('debug_mode', value)

if __name__ == "__main__":
main()


В этом примере:

1. ConfigManager - это Singleton, который управляет конфигурацией приложения.
2. Он загружает настройки из файла JSON при первом создании и сохраняет их при изменении.
3. Различные компоненты приложения (DatabaseManager, Logger, Application) используют один и тот же экземпляр ConfigManager для доступа к настройкам.

Преимущества использования Singleton в данном случае:

1. 🔄 Гарантия единого состояния конфигурации во всем приложении.
2. 💾 Централизованное управление настройками.
3. 🚀 Эффективность: загрузка конфигурации происходит только один раз.
4. 🔒 Контроль доступа: изменения в конфигурации могут быть легко отслежены и управляемы.

Этот пример демонстрирует, как Singleton может быть полезен в реальном приложении для управления глобальным состоянием, таким как конфигурация. 🌟
Принцип SOLID - Single Responsibility (Единственная ответственность) 🎯

Принцип единственной ответственности (SRP) гласит, что каждый класс должен иметь только одну причину для изменения. Это означает, что класс должен быть ответственен только за одну конкретную функциональность. 🧐

Пример на Python: 🐍

# Плохо: класс с множеством ответственностей 😱
class User:
def __init__(self, name):
self.name = name

def get_name(self):
return self.name

def save(self):
# Сохранение пользователя в базу данных
pass

def send_email(self):
# Отправка email пользователю
pass

# Лучше: разделение ответственностей 😎
class User:
def __init__(self, name):
self.name = name

def get_name(self):
return self.name

class UserDB:
def save(self, user):
# Сохранение пользователя в базу данных
pass

class EmailService:
def send_email(self, user):
# Отправка email пользователю
pass


Пример на Rust: 🦀

// Плохо: структура с множеством ответственностей 😱
struct User {
name: String,
}

impl User {
fn new(name: String) -> Self {
User { name }
}

fn get_name(&self) -> &str {
&self.name
}

fn save(&self) {
// Сохранение пользователя в базу данных
}

fn send_email(&self) {
// Отправка email пользователю
}
}

// Лучше: разделение ответственностей 😎
struct User {
name: String,
}

impl User {
fn new(name: String) -> Self {
User { name }
}

fn get_name(&self) -> &str {
&self.name
}
}

struct UserDB;

impl UserDB {
fn save(&self, user: &User) {
// Сохранение пользователя в базу данных
}
}

struct EmailService;

impl EmailService {
fn send_email(&self, user: &User) {
// Отправка email пользователю
}
}


В обоих примерах мы видим, как изначальный класс/структура User с множеством ответственностей разделяется на три отдельных компонента: 🧩
- User для хранения данных пользователя 👤
- UserDB для операций с базой данных 💾
- EmailService для отправки электронных писем ✉️

Это разделение соответствует принципу единственной ответственности, так как теперь каждый класс/структура отвечает только за одну конкретную функциональность. 🎯

Такой подход делает код более:
- Модульным 🧱
- Легче поддерживаемым 🛠
- Расширяемым 🚀

Применение этого принципа поможет вам создавать более чистый и понятный код! 💪💻
🔥 Мультитон: Когда одного экземпляра мало! 🚀

Привет, кодеры! 👋 Сегодня поговорим о паттерне Мультитон - крутом брате Синглтона! 🤓

Мультитон позволяет создать ограниченное количество экземпляров класса, каждый с уникальным ключом. Это как VIP-клуб для объектов! 🎭

Примеры:

🐍 Python:

class Multiton:
_instances = {}

def __new__(cls, key):
if key not in cls._instances:
cls._instances[key] = super().__new__(cls)
return cls._instances[key]

def __init__(self, key):
self.key = key

# Использование
obj1 = Multiton("A")
obj2 = Multiton("B")
obj3 = Multiton("A")

print(obj1 is obj3) # True
print(obj1 is obj2) # False


🐘 PHP:

class Multiton
{
private static $instances = [];

private function __construct($key) {}

public static function getInstance($key)
{
if (!isset(self::$instances[$key])) {
self::$instances[$key] = new static($key);
}
return self::$instances[$key];
}
}

// Использование
$obj1 = Multiton::getInstance("A");
$obj2 = Multiton::getInstance("B");
$obj3 = Multiton::getInstance("A");

var_dump($obj1 === $obj3); // true
var_dump($obj1 === $obj2); // false


🔑 Ключевые моменты:
1️⃣ Ограниченное количество экземпляров
2️⃣ Доступ по уникальному ключу
3️⃣ Глобальная доступность

🔻 Минусы Мультитона:

1️⃣ Нарушение принципа единственной ответственности (SRP) 📏
Мультитон берет на себя две задачи: бизнес-логику и управление своими экземплярами.

2️⃣ Усложнение тестирования 🧪
Глобальное состояние и связи между объектами могут затруднить написание и поддержку unit-тестов.

3️⃣ Скрытые зависимости 🕵️‍♂️
Использование Мультитона может создать неявные связи между компонентами системы.

4️⃣ Проблемы с многопоточностью 🧵
Требуется дополнительная синхронизация для безопасной работы в многопоточной среде.

5️⃣ Сложность сериализации 💾
Может возникнуть проблема при необходимости сериализовать объекты Мультитона.

6️⃣ Нарушение принципа инверсии зависимостей 🔄
Классы, использующие Мультитон, становятся зависимыми от конкретной реализации, а не от абстракции.

7️⃣ Потенциальные проблемы с производительностью 🐌
При неправильном использовании может привести к избыточному потреблению памяти.

8️⃣ Сложность реализации в некоторых языках 🛠
Не все языки программирования имеют удобные механизмы для реализации Мультитона.

Используйте Мультитон, когда нужно контролировать количество экземпляров, но одного недостаточно! 💡
🚀 Мультитон в действии: Управление базами данных! 💾

Представьте, что у вас есть приложение, которое работает с несколькими базами данных:
🔹 Основная БД
🔹 БД для логов
🔹 БД для кэша

Вот как Мультитон может помочь:

🐍 Python пример:

import pymysql

class DatabaseConnection:
_instances = {}

def __new__(cls, db_name):
if db_name not in cls._instances:
cls._instances[db_name] = super().__new__(cls)
return cls._instances[db_name]

def __init__(self, db_name):
if not hasattr(self, 'connection'):
if db_name == 'main':
self.connection = pymysql.connect(host='localhost', user='root', password='password', database='main_db')
elif db_name == 'logs':
self.connection = pymysql.connect(host='logs.example.com', user='logger', password='log_pass', database='logs_db')
elif db_name == 'cache':
self.connection = pymysql.connect(host='cache.example.com', user='cache_user', password='cache_pass', database='cache_db')

def query(self, sql):
with self.connection.cursor() as cursor:
cursor.execute(sql)
return cursor.fetchall()

# Использование
main_db = DatabaseConnection('main')
logs_db = DatabaseConnection('logs')
cache_db = DatabaseConnection('cache')

# Запросы к разным базам
users = main_db.query("SELECT * FROM users")
recent_logs = logs_db.query("SELECT * FROM logs ORDER BY timestamp DESC LIMIT 10")
cached_data = cache_db.query("SELECT * FROM cache WHERE key='home_page'")

# Повторное использование существующего подключения
main_db_again = DatabaseConnection('main')
print(main_db is main_db_again) # True


🔑 Преимущества:
1️⃣ Контроль над количеством подключений к БД
2️⃣ Переиспользование существующих подключений
3️⃣ Удобный доступ к разным БД через единый интерфейс

🚨 Важно: В реальном проекте не храните пароли в коде! Используйте переменные окружения или конфиг-файлы. 😉

💡 Этот паттерн отлично подходит для ситуаций, когда вам нужно управлять несколькими ресурсами одного типа, будь то подключения к БД, кэш-серверы или API-клиенты.
👍1
🏗 Чистая архитектура: путь к гибкому и масштабируемому коду

🔍 Чистая архитектура - это подход к проектированию программного обеспечения, который делает систему независимой от внешних факторов и легко поддерживаемой.

📚 Основные принципы Чистой архитектуры:

🔹 Независимость от фреймворков: архитектура не зависит от существования какой-либо библиотеки
🔹 Тестируемость: бизнес-логика может быть протестирована без UI, базы данных или веб-сервера
🔹 Независимость от UI: интерфейс можно легко изменить без изменения остальной системы
🔹 Независимость от базы данных: можно заменить Oracle на MongoDB без изменения бизнес-правил
🔹 Независимость от внешних сервисов: бизнес-правила не знают ничего о внешнем мире

🔄 Слои Чистой архитектуры:

1️⃣ Entities (Сущности): содержат бизнес-правила предприятия
2️⃣ Use Cases (Сценарии использования): содержат бизнес-правила приложения
3️⃣ Interface Adapters (Адаптеры интерфейса): преобразуют данные из формата, удобного для сценариев и сущностей, в формат, удобный для внешних средств
4️⃣ Frameworks and Drivers (Фреймворки и драйверы): состоят из инструментов, таких как база данных, веб-фреймворк и т.д.

📁 Пример структуры директорий:

clean-architecture/
├── src/
│ ├── entities/
│ │ └── User.ts
│ ├── use-cases/
│ │ └── CreateUser.ts
│ ├── interfaces/
│ │ ├── controllers/
│ │ │ └── UserController.ts
│ │ ├── gateways/
│ │ │ └── UserRepository.ts
│ │ └── presenters/
│ │ └── UserPresenter.ts
│ └── frameworks/
│ ├── database/
│ │ └── MongoUserRepository.ts
│ └── web/
│ └── express/
│ └── routes.ts
├── tests/
└── package.json


💡 Примеры применения:

1. Разработка мобильного приложения: легкая смена UI без изменения логики работы
2. Создание веб-сервиса: возможность быстрого переключения между различными базами данных
3. Корпоративная CRM-система: простота добавления новых функций без нарушения существующих

🧠 Объяснение: Чистая архитектура фокусируется на разделении ответственности и независимости компонентов, а не на оптимизации производительности. Хотя производительность важна, она не является основным принципом Чистой архитектуры.
🟦 Пример Чистой архитектуры на TypeScript:


// src/entities/User.ts
export class User {
constructor(
public id: string,
public name: string,
public email: string
) {}
}

// src/use-cases/CreateUser.ts
import { User } from '../entities/User';
import { UserRepository } from '../interfaces/gateways/UserRepository';

export class CreateUser {
constructor(private userRepository: UserRepository) {}

async execute(userData: { name: string; email: string }): Promise<User> {
const user = new User(Date.now().toString(), userData.name, userData.email);
return this.userRepository.save(user);
}
}

// src/interfaces/gateways/UserRepository.ts
import { User } from '../../entities/User';

export interface UserRepository {
save(user: User): Promise<User>;
}

// src/frameworks/database/MongoUserRepository.ts
import { UserRepository } from '../../interfaces/gateways/UserRepository';
import { User } from '../../entities/User';
import { MongoClient, Db } from 'mongodb';

export class MongoUserRepository implements UserRepository {
private db: Db;

constructor(private client: MongoClient) {
this.db = this.client.db('users_db');
}

async save(user: User): Promise<User> {
await this.db.collection('users').insertOne(user);
return user;
}
}

// src/frameworks/web/express/routes.ts
import express from 'express';
import { CreateUser } from '../../../use-cases/CreateUser';
import { MongoUserRepository } from '../../database/MongoUserRepository';
import { MongoClient } from 'mongodb';

const router = express.Router();
const mongoClient = new MongoClient('mongodb://localhost:27017');
const userRepository = new MongoUserRepository(mongoClient);
const createUser = new CreateUser(userRepository);

router.post('/users', async (req, res) => {
try {
const user = await createUser.execute(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

export default router;
📦 DTO: Ваш надежный курьер данных! 🚚💨

Привет, разработчики! 👋 Сегодня поговорим о DTO (Data Transfer Object) и его применении в мире кода. 🌍💻

🔹 Что такое DTO? 🤔
DTO - это простой объект, который переносит данные между процессами или слоями приложения. Думайте о нем как о контейнере для информации! 📦✨

🔹 Зачем нужен DTO? 🎯
• Уменьшает количество вызовов методов 📉
• Объединяет связанные данные 🤝
• Скрывает внутреннюю структуру данных 🕵️‍♂️

🔹 Где применяется? 🌈
• В многоуровневых приложениях 🏢
• При работе с API 🌐
• В микросервисной архитектуре 🏗️

🔹 Пример использования:
public class UserDTO {
private String name;
private String email;
// геттеры и сеттеры
}


Помните: DTO - это не бизнес-логика, а только данные! 🧠💡

Используйте DTO, чтобы сделать ваш код чище и эффективнее! 🚀✨
🏗️ Микросервисная архитектура: Строим приложения по-новому! 🚀

🧩 Что такое микросервисы?
Это способ создания приложений, где вместо одного большого монолита мы строим множество маленьких, независимых сервисов. Каждый сервис отвечает за свою задачу и может быть разработан, развернут и масштабирован отдельно.

🔍 Пример на Python:

# Сервис пользователей
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/user/<int:user_id>')
def get_user(user_id):
# Здесь была бы логика получения пользователя из БД
return jsonify({"id": user_id, "name": "Иван", "email": "ivan@example.com"})

if __name__ == '__main__':
app.run(port=5000)


🔧 Преимущества:
1️⃣ Гибкость: легко вносить изменения и масштабировать
2️⃣ Устойчивость: проблемы в одном сервисе не затрагивают другие
3️⃣ Технологическое разнообразие: разные сервисы могут использовать разные технологии

⚠️ Но не забываем о вызовах:
- Сложность в управлении множеством сервисов
- Необходимость в надежной системе коммуникации между сервисами

🎓 Начать изучение микросервисов никогда не поздно! Пробуйте, экспериментируйте, и пусть ваш код всегда работает! 💻✨

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

🔍 Зачем нужны микросервисы?

1. Масштабируемость: Легче увеличивать мощность отдельных компонентов системы, а не всего приложения целиком.

2. Гибкость: Можно быстрее вносить изменения и обновлять отдельные части приложения.

3. Технологическая свобода: Разные сервисы могут быть написаны на разных языках и использовать разные технологии.

4. Отказоустойчивость: Проблемы в одном сервисе не приводят к падению всего приложения.

5. Легкость поддержки: Каждый микросервис проще понять и поддерживать из-за его небольшого размера.

🛠️ Как используются микросервисы?

1. Декомпозиция приложения: Большое приложение разбивается на небольшие, независимые сервисы.

2. API-шлюзы: Используются для маршрутизации запросов к нужным микросервисам.

3. Контейнеризация: Часто используется Docker для упаковки и развертывания микросервисов.

4. Оркестрация: Применяются инструменты вроде Kubernetes для управления множеством контейнеров.

5. Мониторинг: Используются специализированные инструменты для отслеживания работы всех сервисов.

🤔 Почему микросервисы?

1. Скорость разработки: Команды могут работать параллельно над разными сервисами.

2. Легкость обновлений: Можно обновлять отдельные сервисы без остановки всего приложения.

3. Повторное использование: Сервисы могут быть использованы в разных проектах.

4. Эффективное использование ресурсов: Можно масштабировать только нужные части приложения.

5. Улучшенная отладка: Легче найти и исправить проблемы в небольших сервисах.

🕰️ Когда использовать микросервисы?

1. Большие, сложные приложения: Когда монолитная архитектура становится трудноуправляемой.

2. Высоконагруженные системы: Где требуется независимое масштабирование компонентов.

3. Быстрорастущие стартапы: Где нужна гибкость для быстрых изменений и роста.

4. Многофункциональные платформы: Где разные функции могут развиваться независимо.

5. Распределенные команды: Когда разные команды работают над разными частями приложения.

⚠️ Важно помнить: микросервисы - не панацея. Они вносят свою сложность в разработку и управление системой. Для небольших проектов или команд монолитная архитектура может быть более подходящим выбором.

📊 Пример использования микросервисов:

Представьте e-commerce платформу:

1. Сервис каталога товаров (Python/Flask)
2. Сервис корзины покупок (Node.js)
3. Сервис оплаты (Java/Spring)
4. Сервис управления пользователями (Go)
5. Сервис рекомендаций (Python/Django)

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