
//C has no vector: an order keeps its lines in an array
//of fixed size and counts them
constexpr size_t maxLines = 8;
//entity
typedef struct {
char sku[16];
int price;
int count;
} OrderLine;
int lineCost(const OrderLine *line) {
return line->price * line->count;
}
//entity: C has no private fields, so the struct is open
//for reading, and only the functions below change it
typedef struct {
int id;
OrderLine lines[maxLines];
size_t size;
bool paid;
} Order;
Order orderMake(int id) {
return (Order){.id = id};
}
//C has no exceptions: a rule that refuses returns the
//reason, and nullptr means done
const char *orderAdd(Order *order, const char *sku,
int price, int count) {
if (count <= 0) {
return "bad count";
}
if (order->size == maxLines) {
return "too many lines";
}
OrderLine *line = &order->lines[order->size++];
snprintf(line->sku, sizeof line->sku, "%s", sku);
line->price = price;
line->count = count;
return nullptr;
}
int orderTotal(const Order *order) {
int sum = 0;
for (size_t i = 0; i < order->size; i++) {
sum += lineCost(&order->lines[i]);
}
return sum;
}
//business rule: 10% off from 200
int orderDiscount(const Order *order) {
int total = orderTotal(order);
return total >= 200 ? total / 10 : 0;
}
int orderPayable(const Order *order) {
return orderTotal(order) - orderDiscount(order);
}
//business rule: an empty order is never paid
const char *orderPay(Order *order) {
if (order->size == 0) {
return "empty order";
}
order->paid = true;
return nullptr;
}
//client code
//no database and no screen anywhere above, and no
//header beyond the standard library
Order order = orderMake(1001);
orderAdd(&order, "pen", 30, 2);
orderAdd(&order, "lamp", 190, 1);
printf("order %d\n", order.id);
printf("lines %zu\n", order.size);
printf("total %d\n", orderTotal(&order));
printf("discount %d\n", orderDiscount(&order));
printf("payable %d\n", orderPayable(&order));
orderPay(&order);
printf("paid %s\n", order.paid ? "true" : "false");
//a line that breaks the rule never gets into the order,
//so the order below stays empty and is refused too
Order empty = orderMake(1002);
const char *reason = orderAdd(&empty, "pen", 30, 0);
if (reason != nullptr) {
printf("refused: %s\n", reason);
}
reason = orderPay(&empty);
if (reason != nullptr) {
printf("refused: %s\n", reason);
}
//printed: order 1001
// lines 2
// total 250
// discount 25
// payable 225
// paid true
// refused: bad count
// refused: empty order
An entity holds a business rule of the shop and knows nothing about a database, a screen or the network, so it can be tested without them.Read more问题。商店对金额达到 200 的每笔订单减免 10%。如果这条规则写在网页里,又在数据库查询里写了一遍,两份副本很快就会不一致。要检查这条规则,就得启动网页或数据库。商店最重要的知识分散各处,难以测试。 思路。实体是一段数据,连同与它相关的业务规则:订单包含什么,总额如何计算,何时可以付款。它对数据库、界面或网络一无所知。整洁架构有一条主要规则,即依赖规则:外层代码可以了解内层代码,但内层代码从不了解外层代码。实体是最内层的代码,因此不依赖任何东西。 如何阅读示例。示例中有两个实体:订单行和订单本身。订单汇总各行,对 200 及以上的金额减免 10%,并计算应付金额。订单为空时,它拒绝付款。客户端代码构建含两个订单行的订单 1001,并打印其中的数字:总额 250,折扣 25,应付 225。它还尝试为一个空订单付款,实体予以拒绝。在某些语言中,实体是带有函数的普通值,而不是类;它的角色不变。 何时使用。业务中每个拥有自己规则的概念,例如订单或账户,都应做成一个实体。没有规则的数据不需要实体:简单的结构就够了。 常见错误。实体自己把自己保存到数据库,或者为界面格式化自己。这样它就依赖于外层代码,它的规则也无法再单独测试。 |