在PHP开发中,贫血模型(Anemic Domain Model)和充血模型(Rich Domain Model)是两种截然不同的设计范式。这两种模式的核心差异在于业务逻辑的存放位置和对象的职责分配。
贫血模型的特点是:
充血模型的特点则是:
重要提示:贫血模型并非完全错误的设计,在某些简单场景下它可能是合理的选择。但在复杂的业务系统中,充血模型通常能带来更好的可维护性。
php复制class Order {
private $items;
private $total;
public function getItems() { return $this->items; }
public function setItems($items) { $this->items = $items; }
public function getTotal() { return $this->total; }
public function setTotal($total) { $this->total = $total; }
}
class OrderService {
public function calculateTotal(Order $order) {
$items = $order->getItems();
$total = 0;
foreach ($items as $item) {
$total += $item->price * $item->quantity;
}
if (count($items) > 5) {
$total *= 0.9; // 超过5件打9折
}
$order->setTotal($total);
}
public function validate(Order $order) {
// 各种验证逻辑...
}
}
这种实现方式的问题在于:
php复制class Order {
private $items;
private $total;
public function calculateTotal(): float {
$total = 0;
foreach ($this->items as $item) {
$total += $item->price * $item->quantity;
}
if ($this->hasDiscount()) {
$total *= 0.9;
}
$this->total = $total;
return $total;
}
private function hasDiscount(): bool {
return count($this->items) > 5;
}
public function validate(): bool {
// 验证逻辑...
}
}
class OrderService {
public function processOrder(Order $order) {
if (!$order->validate()) {
throw new InvalidOrderException();
}
$total = $order->calculateTotal();
// 其他处理流程...
}
}
充血模型的优势体现在:
贫血模型在以下场景可能是合理的选择:
经验分享:在实际项目中,我见过很多Laravel项目默认采用了贫血模型,因为Eloquent模型主要关注数据持久化而非业务逻辑。这在小型项目中确实能加快开发速度。
充血模型更适合以下场景:
典型的使用模式包括:
假设我们有一个UserService包含以下方法:
php复制class UserService {
public function changePassword(User $user, $newPassword) {
if (strlen($newPassword) < 8) {
throw new InvalidPasswordException();
}
if ($user->isPasswordTooOld()) {
throw new PasswordExpiredException();
}
$user->setPasswordHash(password_hash($newPassword, PASSWORD_DEFAULT));
$user->setLastPasswordChange(new DateTime());
}
}
可以重构为:
php复制class User {
// ...其他属性和方法
public function changePassword($newPassword) {
if (strlen($newPassword) < 8) {
throw new InvalidPasswordException();
}
if ($this->isPasswordTooOld()) {
throw new PasswordExpiredException();
}
$this->passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
$this->lastPasswordChange = new DateTime();
}
private function isPasswordTooOld(): bool {
// 检查密码是否过期
}
}
class UserService {
public function changePassword(User $user, $newPassword) {
$user->changePassword($newPassword);
}
}
问题1:实体类变得过于庞大
问题2:循环依赖
问题3:性能考虑
充血模型鼓励我们将相关行为放在同一个类中,但这并不意味着类应该承担过多职责。一个好的实践是:
充血模型天然符合迪米特法则(最少知识原则),因为:
充血模型的一个显著优势是更易于测试:
例如,测试订单总价计算:
php复制public function testOrderTotalCalculation() {
$order = new Order();
$order->addItem(new Item(100, 2)); // 单价100,数量2
$order->addItem(new Item(50, 3)); // 单价50,数量3
$this->assertEquals(350, $order->calculateTotal());
}
虽然Laravel默认倾向于贫血模型,但我们仍然可以实现充血模型:
php复制class Order extends Model {
// ...Eloquent关系和方法
public function calculateTotal(): float {
return $this->items->sum(function($item) {
return $item->price * $item->quantity;
});
}
public function applyDiscount(float $percentage) {
if ($percentage < 0 || $percentage > 100) {
throw new InvalidDiscountException();
}
$this->total *= (1 - $percentage/100);
$this->save();
}
}
Symfony的Doctrine实体也支持充血模型:
php复制/**
* @ORM\Entity
*/
class Product {
/**
* @ORM\Column(type="decimal", precision=10, scale=2)
*/
private $price;
/**
* @ORM\Column(type="integer")
*/
private $stock;
public function purchase(int $quantity): void {
if ($quantity <= 0) {
throw new InvalidQuantityException();
}
if ($quantity > $this->stock) {
throw new OutOfStockException();
}
$this->stock -= $quantity;
}
public function updatePrice(float $newPrice): void {
if ($newPrice <= 0) {
throw new InvalidPriceException();
}
$this->price = $newPrice;
}
}
虽然充血模型有很多优点,但在性能敏感的场景需要考虑以下因素:
优化策略包括:
在实际项目中,我通常会:
引入充血模型需要考虑团队因素:
一个实用的过渡策略是:
充血模型是领域驱动设计(DDD)的核心组成部分。要进一步发挥其价值,可以考虑:
例如,在电商系统中:
php复制class Order {
private $lines; // OrderLine集合
public function addProduct(Product $product, int $quantity) {
// 业务规则检查...
$this->lines[] = new OrderLine($product, $quantity);
}
public function confirm() {
$this->status = OrderStatus::CONFIRMED;
$this->recordEvent(new OrderConfirmed($this->id));
}
}
class OrderLine {
private $product;
private $quantity;
private $unitPrice;
public function __construct(Product $product, int $quantity) {
$this->product = $product;
$this->quantity = $quantity;
$this->unitPrice = $product->getPrice();
}
public function getSubtotal(): Money {
return $this->unitPrice->multiply($this->quantity);
}
}
这种设计清晰地表达了领域概念,使代码更贴近业务语言。