
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
//entity
class OrderLine {
string sku;
int price;
int count;
public:
OrderLine(string sku, int price, int count)
: sku(sku), price(price), count(count) {
if (count <= 0) {
throw invalid_argument("bad count");
}
}
int cost() const {
return price * count;
}
};
//entity
class Order {
int id;
vector<OrderLine> lines;
bool isPaid = false;
public:
explicit Order(int id): id(id) {}
int number() const {
return id;
}
void add(const string& sku, int price, int count) {
lines.push_back(OrderLine(sku, price, count));
}
int total() const {
int sum = 0;
for (const OrderLine& line : lines) {
sum += line.cost();
}
return sum;
}
//business rule: 10% off from 200
int discount() const {
return total() >= 200 ? total() / 10 : 0;
}
int payable() const {
return total() - discount();
}
//business rule: an empty order is never paid
void pay() {
if (lines.empty()) {
throw logic_error("empty order");
}
isPaid = true;
}
bool paid() const {
return isPaid;
}
int size() const {
return lines.size();
}
};
//client code
//no database and no screen anywhere above, and no
//header beyond the standard library
Order order(1001);
order.add("pen", 30, 2);
order.add("lamp", 190, 1);
cout << "order " << order.number() << endl;
cout << "lines " << order.size() << endl;
cout << "total " << order.total() << endl;
cout << "discount " << order.discount() << endl;
cout << "payable " << order.payable() << endl;
order.pay();
cout << "paid " << boolalpha << order.paid() << endl;
//a line that breaks the rule never gets into the order,
//so the order below stays empty and is refused too
Order empty(1002);
try {
empty.add("pen", 30, 0);
} catch (const invalid_argument& error) {
cout << "refused: " << error.what() << endl;
}
try {
empty.pay();
} catch (const logic_error& error) {
cout << "refused: " << error.what() << endl;
}
//printed: order 1001
// lines 2
// total 250
// discount 25
// payable 225
// paid true
// refused: bad count
// refused: empty order
Сущность держит правило предметной области и ничего не знает ни о базе, ни об экране, ни о сети, поэтому её можно проверить без них.Read moreЗадача. Магазин даёт скидку 10% на любой заказ от 200. Если это правило живёт на веб-странице и ещё раз в запросе к базе данных, две копии скоро разойдутся. Чтобы проверить правило, приходится запускать страницу или базу. Самое важное знание магазина разбросано, и проверить его трудно. Идея. Сущность - это данные вместе с бизнес-правилами, которые к ним относятся: из чего состоит заказ, как считается его итог, когда его можно оплатить. О базе данных, экране или сети она ничего не знает. В чистой архитектуре есть одно главное правило - правило зависимостей: внешний код может знать внутренний, но внутренний никогда не знает внешнего. Сущности - самый внутренний код, поэтому они ни от чего не зависят. Как читать пример. Сущностей две: позиция заказа и сам заказ. Заказ складывает свои позиции, даёт скидку 10% при сумме от 200 и считает сумму к оплате. Пока заказ пуст, он отказывается принимать оплату. Клиентский код собирает заказ 1001 из двух позиций и печатает его числа: итог 250, скидка 25, к оплате 225. Ещё он пытается оплатить пустой заказ, и сущность отказывает. В некоторых языках сущность - не класс, а обычное значение с функциями; роль у неё та же. Когда применять. Сущность стоит заводить для каждого понятия бизнеса, у которого есть свои правила, например для заказа или счёта. Данным без правил сущность не нужна: хватит простой структуры. Частая ошибка. Сущность, которая сама сохраняет себя в базу данных или оформляет себя для экрана. Тогда она зависит от внешнего кода, и её правила уже нельзя проверить отдельно. |