Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое анемичная модель (Anemic Domain Model)?
Анемичная модель — это объектная модель, в которой классы данных содержат только состояние (поля, свойства) и практически не содержат поведения (методов бизнес-логики). Эти классы являются простыми структурами для передачи данных между слоями приложения, а вся логика работы с ними сосредоточена в отдельных сервисах, менеджерах или контроллерах.
Этот термин был популяризирован Мартином Фаулером в его статье «AnemicDomainModel», где он критиковал такой подход как анти-паттерн в контексте объектно-ориентированного дизайна.
Ключевые характеристики анемичной модели
- Отсутствие поведения: Классы представляют собой набор публичных свойств (
get/set) без методов, реализующих бизнес-правила или инварианты. - Разделение данных и логики: Бизнес-операции выполняются внешними классами-сервисами, которые манипулируют этими "пустыми" объектами.
- Схожесть с DTO: Часто такие модели используются исключительно для передачи данных (как Data Transfer Object), но применяются повсеместно в доменном слое.
Пример анемичной модели в Swift
Рассмотрим типичную модель заказа в приложении.
// Анемичная модель: только данные, нет логики
struct Order {
var id: UUID
var items: [OrderItem]
var totalAmount: Decimal
var status: OrderStatus // e.g., .pending, .paid, .shipped
}
Все операции с этим заказом будут выполняться в другом месте:
// Сервис, содержащий всю бизнес-логику
class OrderService {
func calculateTotal(for order: Order) -> Decimal {
// Вычисление суммы, возможно с применением скидок, налогов
let itemsTotal = order.items.reduce(0) { $0 + $1.price }
// ... сложная логика
order.totalAmount = itemsTotal
return itemsTotal
}
func markAsPaid(_ order: Order) {
// Проверка возможности оплаты, изменение статуса
guard order.status == .pending else { return }
order.status = .paid
// ... дополнительные действия: обновление склада, отправка уведомления
}
func validate(_ order: Order) -> Bool {
// Проверка корректности заказа
return !order.items.isEmpty && order.totalAmount > 0
}
}
Сравнение с "насыщенной" (Rich) моделью
В противоположность анемичной, "насыщенная" доменная модель инкапсулирует данные и поведение внутри одного класса.
// Насыщенная модель: данные и логика вместе
struct Order {
private(set) var id: UUID
private(set) var items: [OrderItem]
private(set) var totalAmount: Decimal
private(set) var status: OrderStatus
init(items: [OrderItem]) {
self.id = UUID()
self.items = items
self.status = .pending
self.calculateTotal() // Логика внутри модели
}
// Бизнес-логика инкапсулирована в методы модели
private mutating func calculateTotal() {
let baseTotal = items.reduce(0) { $0 + $1.price }
// Применение правил расчета внутри модели
self.totalAmount = applyDiscounts(to: baseTotal)
}
mutating func markAsPaid() throws {
guard status == .pending else {
throw OrderError.invalidStateTransition
}
// Все проверки и побочные эффекты управляются здесь
status = .paid
// Модель может управлять своими внутренними изменениями
updateInventory()
}
func validate() -> Bool {
return !items.isEmpty && totalAmount > 0
}
// Приватные методы для внутренней логики
private func applyDiscounts(to amount: Decimal) -> Decimal { ... }
private func updateInventory() { ... }
}
Почему анемичная модель считается проблемой?
- Нарушение принципов ООП: Объектно-ориентированный дизайн предполагает объединение данных и поведения. Анемичная модель превращает объекты в простые структуры, сводя ООП к процедурному стилю.
- Потеря инкапсуляции: Бизнес-правила и валидация оказываются снаружи, что позволяет изменять состояние объекта в неконтролируемых комбинациях из разных сервисов. Это ведет к:
* **Распылению логики:** Одни и те же правила могут быть дублированы или противоречить друг другу в разных сервисах.
* **Сложности поддержки инвариантов:** Гарантировать корректное состояние объекта (например, `totalAmount` всегда соответствует сумме `items`) становится трудно.
- Снижение выразительности модели: Модель не "говорит" о своих возможностях. Вместо естественного
order.markAsPaid()мы видимorderService.markAsPaid(order).
Когда анемичная модель может быть приемлемой?
- Простой CRUD: Для приложений без сложной бизнес-логики, где сущности действительно являются лишь данными для записи/чтения (например, админ-панель).
- Слои персистентности или представления: Использование простых структур как DTO для передачи между API и UI или между базой данных и сервисом — это нормальная практика. Проблема возникает, когда эти DTO становятся единственным представлением домена в системе.
- Производительность или ограничения: В некоторых архитектурах (например, строгий Clean Architecture) разделение данных и логики может быть сознательным выбором для обеспечения тестируемости или изоляции слоев.
Заключение
Анемичная модель — это паттерн, который следует применять с осторожностью. Для сложных систем с богатой бизнес-логикой он приводит к хрупкости, дублированию и нарушению инкапсуляции. Разработчик должен стремиться к созданию "насыщенных" моделей, где объекты являются полноценными участниками системы, отвечающими за свое состояние и поведение. Однако в простых случаях или на границах слоев приложения использование простых структур данных может быть оправданным и практичным. Ключ — в понимании контекста и сознательном выборе подхода.