
//entity
struct OrderLine {
sku: String,
price: i64,
count: i64,
}
impl OrderLine {
//Rust has no exceptions: a broken rule comes back
//as a value, and the caller cannot ignore it
fn new(sku: &str, price: i64, count: i64)
-> Result<OrderLine, String> {
if count <= 0 {
return Err(format!("bad count for {}", sku));
}
Ok(OrderLine {
sku: sku.to_string(),
price,
count,
})
}
fn cost(&self) -> i64 {
self.price * self.count
}
}
//entity
struct Order {
id: i64,
lines: Vec<OrderLine>,
paid: bool,
}
impl Order {
fn new(id: i64) -> Order {
Order {
id,
lines: Vec::new(),
paid: false,
}
}
//adding changes the order, so it asks for &mut self;
//reading rules below is enough with &self
fn add(&mut self, line: OrderLine) {
self.lines.push(line);
}
fn goods(&self) -> String {
let names: Vec<&str> = self.lines.iter()
.map(|line| line.sku.as_str())
.collect();
names.join(" ")
}
fn total(&self) -> i64 {
self.lines.iter().map(|line| line.cost()).sum()
}
//business rule: 10% off from 200
fn discount(&self) -> i64 {
let total = self.total();
if total >= 200 {
total / 10
} else {
0
}
}
fn payable(&self) -> i64 {
self.total() - self.discount()
}
//business rule: an empty order is never paid
fn pay(&mut self) -> Result<(), String> {
if self.lines.is_empty() {
return Err("empty order".to_string());
}
self.paid = true;
Ok(())
}
}
//client code
//no database and no screen anywhere above
let mut order = Order::new(1001);
order.add(OrderLine::new("pen", 30, 2).unwrap());
order.add(OrderLine::new("lamp", 190, 1).unwrap());
println!("order {}", order.id);
println!("lines {}", order.lines.len());
println!("goods {}", order.goods());
println!("total {}", order.total());
println!("discount {}", order.discount());
println!("payable {}", order.payable());
order.pay().unwrap();
println!("paid {}", order.paid);
match OrderLine::new("pen", 30, 0) {
Ok(_) => println!("line added"),
Err(reason) => println!("refused: {}", reason),
}
match Order::new(1002).pay() {
Ok(()) => println!("paid"),
Err(reason) => println!("refused: {}", reason),
}
//printed: order 1001
// lines 2
// goods pen lamp
// total 250
// discount 25
// payable 225
// paid true
// refused: bad count for pen
// 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。它还尝试为一个空订单付款,实体予以拒绝。在某些语言中,实体是带有函数的普通值,而不是类;它的角色不变。 何时使用。业务中每个拥有自己规则的概念,例如订单或账户,都应做成一个实体。没有规则的数据不需要实体:简单的结构就够了。 常见错误。实体自己把自己保存到数据库,或者为界面格式化自己。这样它就依赖于外层代码,它的规则也无法再单独测试。 |