后端工程师对于MVC架构肯定一点也不陌生,Android官网对于三层架构的描述如下图,大体意思就是UI(Web)、数据层,控制层做UI和数据的传输和处理。
1. 基于贫血模型
基于贫血模型的传统开发模式是一种传统的软件开发方式,其中数据和业务逻辑通常被分离到不同的层次,而数据对象(Model)主要负责存储数据而缺乏业务行为。
// Product.java
public class Product {
private String name;
private double price;
// 省略构造方法和 getters/setters
}
// ShoppingCart.java
public class ShoppingCart {
private List<Product> items = new ArrayList<>();
// 省略添加商品、计算总价等业务逻辑方法
}
// ShoppingCartController.java
@Controller
@RequestMapping("/cart")
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
@GetMapping("/add/{productId}")
public String addToCart(@PathVariable("productId") int productId) {
// 通过 ProductService 获取 Product 信息
Product product = productService.getProductById(productId);
// 将商品添加到购物车
shoppingCartService.addToCart(product);
return "redirect:/cart";
}
@GetMapping
public String viewCart(Model model) {
// 获取购物车信息
ShoppingCart cart = shoppingCartService.getCart();
// 将购物车信息传递给视图
model.addAttribute("cart", cart);
return "cart";
}
// 省略其他购物车操作的方法
}
在这个例子中:
ShoppingCartController是一个Spring MVC的控制器,它负责处理与购物车相关的HTTP请求。Product和ShoppingCart是数据对象,负责存储商品和购物车信息。ShoppingCartService是一个服务类,负责购物车的业务逻辑。
2. 充血模型的 DDD 开发模式?
在贫血模型中,数据和业务逻辑被分割到不同的类中。充血模型(Rich Domain Model)正好相反,数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。
数据对象(Model):
// Product.java
public class Product {
private String name;
private double price;
// 充血模型:添加业务逻辑到数据对象中
public double calculateDiscountedPrice(double discountPercentage) {
double discount = price * (discountPercentage / 100);
return price - discount;
}
// 省略其他构造方法和 getters/setters
}
// ShoppingCart.java
public class ShoppingCart {
private List<Product> items = new ArrayList<>();
// 充血模型:添加业务逻辑到数据对象中
public double calculateTotalPrice() {
double totalPrice = 0;
for (Product item : items) {
totalPrice += item.getPrice();
}
return totalPrice;
}
public void addToCart(Product product) {
items.add(product);
}
// 省略其他购物车相关的业务逻辑方法
}
控制器(Controller):
// ShoppingCartController.java
@Controller
@RequestMapping("/cart")
public class ShoppingCartController {
@Autowired
private ProductService productService;
@GetMapping("/add/{productId}")
public String addToCart(@PathVariable("productId") int productId) {
// 通过 ProductService 获取 Product 信息
Product product = productService.getProductById(productId);
// 获取购物车信息
ShoppingCart cart = getOrCreateCart();
// 将商品添加到购物车
cart.addToCart(product);
return "redirect:/cart";
}
@GetMapping
public String viewCart(Model model) {
// 获取购物车信息
ShoppingCart cart = getOrCreateCart();
// 将购物车信息传递给视图
model.addAttribute("cart", cart);
return "cart";
}
// 省略其他购物车操作的方法
private ShoppingCart getOrCreateCart() {
// 从用户会话或数据库中获取购物车信息
// 如果购物车不存在,则创建一个新的购物车对象
// 返回购物车对象
}
}
在这个示例中:
Product和ShoppingCart类中包含了一些业务逻辑,例如计算商品打折后价格和计算购物车总价。ShoppingCartController中的购物车操作方法更加简化,因为购物车对象(ShoppingCart)本身具有处理购物车操作的能力。
3. 什么项目应该使用基于充血模型开发模式?
贫血模型和充血模型区别不就是一个将业务逻辑放到Service 类中,一个将业务逻辑放到 Domain 领域模型中吗?为什么基于贫血模型的传统开发模式,就不能应对复杂业务系统的开发?而基于充血模型的 DDD 开发模式就可以呢?
基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD 开发模式有点大材小用,无法发挥作用。相反,对于业务复杂的系统开发来说,基于充血模型的 DDD 开发模式,因为前期需要在设计上投入更多时
间和精力,来提高代码的复用性和可维护性,所以相比基于贫血模型的开发模式,更加有优势。
其实就是一个是面向对象,一个是面向数据库编程。
4.那讲了那么多,充血模型与贫血模型,跟前面的面向对象又有什么关系呢?
假设上面的商品有水果,有数码产品呢?
水果有什么参数?
电子产品有什么参数呢
例如购物车中有多种商品类型,一个是苹果水果,一个是苹果手机(存在一些字段在不同的类型下被复用)。
-
若是贫血模型:
- 使用一个大DTO,就会势必在service中通过if...else来进行,就会造成代码腐化;
- 若是使用继承方式,每一种子类型都是一个DTO,需要路由到子service(策略服务)完成个性化处理;
-
若是充血模型:
- 若是使用继承,只需要抽出公共类,把商品价格等属性放在公共类里,其余的属性以及逻辑放到子类里执行;避免杂乱的service类。
在领域模型中出现类似继承、多态的情况,则应该继承与多态的部分以充血对象的形式进行实现,每个对象处理处理自己的添加购物车,处理价格,处理参数。