06 基于贫血模型和充血模型?

199 阅读4分钟

后端工程师对于MVC架构肯定一点也不陌生,Android官网对于三层架构的描述如下图,大体意思就是UI(Web)、数据层,控制层做UI和数据的传输和处理。 image.png

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请求。
  • ProductShoppingCart 是数据对象,负责存储商品和购物车信息。
  • 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() {
        // 从用户会话或数据库中获取购物车信息
        // 如果购物车不存在,则创建一个新的购物车对象
        // 返回购物车对象
    }
}

在这个示例中:

  • ProductShoppingCart 类中包含了一些业务逻辑,例如计算商品打折后价格和计算购物车总价。
  • ShoppingCartController 中的购物车操作方法更加简化,因为购物车对象(ShoppingCart)本身具有处理购物车操作的能力。

3. 什么项目应该使用基于充血模型开发模式?

贫血模型和充血模型区别不就是一个将业务逻辑放到Service 类中,一个将业务逻辑放到 Domain 领域模型中吗?为什么基于贫血模型的传统开发模式,就不能应对复杂业务系统的开发?而基于充血模型的 DDD 开发模式就可以呢?

基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD 开发模式有点大材小用,无法发挥作用。相反,对于业务复杂的系统开发来说,基于充血模型的 DDD 开发模式,因为前期需要在设计上投入更多时 间和精力,来提高代码的复用性和可维护性,所以相比基于贫血模型的开发模式,更加有优势。 其实就是一个是面向对象,一个是面向数据库编程。

4.那讲了那么多,充血模型与贫血模型,跟前面的面向对象又有什么关系呢?

假设上面的商品有水果,有数码产品呢?

水果有什么参数?

image.png

电子产品有什么参数呢

image.png

例如购物车中有多种商品类型,一个是苹果水果,一个是苹果手机(存在一些字段在不同的类型下被复用)。

  • 若是贫血模型:

    • 使用一个大DTO,就会势必在service中通过if...else来进行,就会造成代码腐化;
    • 若是使用继承方式,每一种子类型都是一个DTO,需要路由到子service(策略服务)完成个性化处理;
  • 若是充血模型:

    • 若是使用继承,只需要抽出公共类,把商品价格等属性放在公共类里,其余的属性以及逻辑放到子类里执行;避免杂乱的service类。

在领域模型中出现类似继承、多态的情况,则应该继承与多态的部分以充血对象的形式进行实现,每个对象处理处理自己的添加购物车,处理价格,处理参数。