• 设计模式(三)面向对象:贫血和充血模型下的MVC


    贫血模型和充血模型

    在我们日常的开发工作总,MVC是必不可少的开发架构,MVC架构总共分为展示层,逻辑层,数据层。

    贫血模型

    细分一下展示层一般包含Controller层,controller负责获取数据;逻辑层一般包含Service,Service层主要负责业务的真正的处理逻辑,而Controller层负责调用Service中的函数;数据层主要包含Entity用对象来存储数据以及Respository进行数据的访问。(当然不同的开发可能对名字的定义不同,这里就用我常用的方法)

    通过上面的描述我们可以看到正常的MVC架构其实是数据在Dao和Entity中,但是真正的处理逻辑是在Service之中的,那么这种架构我们一般称他为贫血模型,将数据和业务逻辑分开处理。而这种架构也破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。

    充血模型

    与贫血模型相对应的就是充血模型了,充血模型就是将数据和业务逻辑统一放在一个类中,是完全基于面向对象的特性来设计的。

    两者的区别

    首先,虽然贫血模型违反了OOP的封装特性,但是现在几乎所有Web项目都是基于这个模型来进行开发的,这已经成了开发人员的一种习惯。但是面向过程的编程风格也有很大的弊端,比如数据和操作分离之后,数据本身就不受限制了,任何代码都可以随意的修改数据。

    目前贫血模型还在受欢迎的情况主要是以下几个原因

    • 第一个原因就是大部分情况下,系统中的业务逻辑还都比较简单,只设计到CRUD操作,所以不需要精心设计充血模型,贫血模型足够应付这些简单的业务开发工作,
    • 第二个原因就是充血模型的架构设计比较复杂,需要在开始开发之前就要想好要暴露哪些操作,定义哪些业务逻辑。而贫血模型就相对简单。
    • 第三个原因就是大家的思想已经固化,一直一来大家都是使用的贫血模型,就没必要花功夫去学习和实现充血模型了。

    看上去充血模型好像只是吧业务逻辑放到了数据中统一处理,但其实没那么简单,分别对应的是两个不同的开发流程。

    贫血模型的开发流程,也就是现在常用的流程,就是业务需求来了之后,我们再数据库中找到对应的库和表,然后对SQL语句进行编写,再对各层级的数据代码进行修改。而整个业务逻辑都是针对SQL语句进行开发,并且每个语句和开发都是针对特定的业务场景,复用性差,这样依赖,当业务过多的时候就会发现SQL太多,越来越混乱导致无法维护。而充血模型能在系统越来越大,业务越来越复杂的情况下使得代码的拓展性以及复用性更强,而这也是我们面向对象特性所需要的。

    基于充血模型的虚拟钱包系统

    比如我们现在要开发一个虚拟钱包系统,账户对应余额,交易记录等属性,我们可以通过充值,提现,支付,转账对账户进行处理。
    在这里插入图片描述
    这里我们就根据MVC的架构分别尝试贫血模型和充血模型的代码开发,首先,框架我们主要分为Controller,Entity,Service,Repository四层,贫血和充血模型的主要区别在Entity和Service层。

    贫血模型

    对于钱包支出,收入,转账三个需求,在贫血模型我们都将她们放在Service层进行实现,Entity就只包含一些属性相关的内容。

    package OOP.VirtualWalletAnemia.entity;
    
    import java.math.BigDecimal;
    
    /**
     * @time: 2022/10/19
     * @author: yuanyongan
     * @description: 钱包实例,只对数据进行存储,不进行业务需求开发
     */
    public class VirtualWalletEntity {
        private Long id;
        private Long creatTime;
        private BigDecimal balance;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Long getCreatTime() {
            return creatTime;
        }
    
        public void setCreatTime(Long creatTime) {
            this.creatTime = creatTime;
        }
    
        public BigDecimal getBalance() {
            return balance;
        }
    
        public void setBalance(BigDecimal balance) {
            this.balance = balance;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    package OOP.VirtualWalletAnemia.service;
    
    import OOP.VirtualWalletAnemia.common.Status;
    import OOP.VirtualWalletAnemia.entity.VirtualWalletEntity;
    import OOP.VirtualWalletAnemia.entity.VirtualWalletTransactionEntity;
    import OOP.VirtualWalletAnemia.repository.VirtualWalletRepository;
    import OOP.VirtualWalletAnemia.repository.VirtualWalletTransactionRepository;
    
    import java.math.BigDecimal;
    
    /**
     * @time: 2022/10/19
     * @author: yuanyongan
     * @description: 实际业务处理流程,通过调用Repository对数据进行获取
     */
    public class VirtualWalletService {
        // 通过构造函数或者IOC框架注入
        private VirtualWalletRepository walletRepo;
        private VirtualWalletTransactionRepository transactionRepo;
        public VirtualWalletEntity getWalletEntity(Long walletId){
            // 根据id返回钱包对象
            return walletRepo.getWalletEntity(walletId);
        }
        public BigDecimal getBalance(Long walletId){
            // 根据id返回钱包对象的余额
            return walletRepo.getBalance(walletId);
        }
    
        // 余额支出逻辑
        public void debit(Long walletId, BigDecimal amount){
            // 实际场景需要通过id获取entity
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            BigDecimal balance = walletEntity.getBalance();
            if (balance.compareTo(amount) > 0){
                walletRepo.updateBalance(walletId, balance.subtract(amount));
            }
        }
    
        // 余额收入逻辑
        public void credit(Long walletId, BigDecimal amount){
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            BigDecimal balance = walletEntity.getBalance();
            walletRepo.updateBalance(walletId, balance.add(amount));
        }
    
        // 转账逻辑
        public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount){
            VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
            transactionEntity.setAmount(amount);
            transactionEntity.setCreateTime(System.currentTimeMillis());
            transactionEntity.setFromWalletId(fromWalletId);
            transactionEntity.setToWalletId(toWalletId);
            transactionEntity.setStatus(Status.TO_BE_EXECUTED);
            Long transactionId = transactionRepo.saveTransaction(transactionEntity);
            try{
                debit(fromWalletId, amount);
                credit(toWalletId, amount);
            }catch (Exception e){
                transactionRepo.updateStatus(transactionId, Status.FAILED);
                // .. 处理异常
            }
            transactionRepo.updateStatus(transactionId, Status.SUCCEED);
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    充血模型

    充血模型与贫血模型主要的不同就是将跟虚拟钱包相关的逻辑工作放在Entity中,而Service依赖Entity实现

    package OOP.VirtualWalletCongestion.entity;
    
    import java.math.BigDecimal;
    
    /**
     * @time: 2022/10/19
     * @author: yuanyongan
     * @description: 钱包实例,只对数据进行存储,不进行业务需求开发
     */
    public class VirtualWalletEntity {
        private Long id;
        private Long creatTime;
        private BigDecimal balance;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Long getCreatTime() {
            return creatTime;
        }
    
        public void setCreatTime(Long creatTime) {
            this.creatTime = creatTime;
        }
    
        public BigDecimal getBalance() {
            return balance;
        }
    
        public void setBalance(BigDecimal balance) {
            this.balance = balance;
        }
    
        public void debit(BigDecimal amount){
            if (this.balance.compareTo(amount) > 0){
                this.balance.subtract(amount);
            }
        }
        public void credit(BigDecimal amount){
            if (amount.compareTo(BigDecimal.ZERO) > 0){
                this.balance.add(amount);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    package OOP.VirtualWalletCongestion.service;
    
    import OOP.VirtualWalletCongestion.common.Status;
    import OOP.VirtualWalletCongestion.entity.VirtualWalletEntity;
    import OOP.VirtualWalletCongestion.entity.VirtualWalletTransactionEntity;
    import OOP.VirtualWalletCongestion.repository.VirtualWalletRepository;
    import OOP.VirtualWalletCongestion.repository.VirtualWalletTransactionRepository;
    
    import java.math.BigDecimal;
    
    /**
     * @time: 2022/10/19
     * @author: yuanyongan
     * @description: 实际业务处理流程,通过调用Repository对数据进行获取
     */
    public class VirtualWalletService {
        // 通过构造函数或者IOC框架注入
        private VirtualWalletRepository walletRepo;
        private VirtualWalletTransactionRepository transactionRepo;
        public VirtualWalletEntity getWalletEntity(Long walletId){
            // 根据id返回钱包对象
            return walletRepo.getWalletEntity(walletId);
        }
        public BigDecimal getBalance(Long walletId){
            // 根据id返回钱包对象的余额
            return walletRepo.getBalance(walletId);
        }
    
        // 余额支出逻辑
        public void debit(Long walletId, BigDecimal amount){
            // 实际场景需要通过id获取entity
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            walletEntity.debit(amount);
            walletRepo.updateBalance(walletId, walletEntity.getBalance());
        }
    
        // 余额收入逻辑
        public void credit(Long walletId, BigDecimal amount){
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            walletEntity.credit(amount);
            walletRepo.updateBalance(walletId, walletEntity.getBalance());
        }
    
        // 转账逻辑
        public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount){
            VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
            transactionEntity.setAmount(amount);
            transactionEntity.setCreateTime(System.currentTimeMillis());
            transactionEntity.setFromWalletId(fromWalletId);
            transactionEntity.setToWalletId(toWalletId);
            transactionEntity.setStatus(Status.TO_BE_EXECUTED);
            Long transactionId = transactionRepo.saveTransaction(transactionEntity);
            try{
                debit(fromWalletId, amount);
                credit(toWalletId, amount);
            }catch (Exception e){
                transactionRepo.updateStatus(transactionId, Status.FAILED);
                // .. 处理异常
            }
            transactionRepo.updateStatus(transactionId, Status.SUCCEED);
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    思考

    我们可以看到,充血模型将虚拟钱包的相关功能放在了类中,这与面向对象的封装特性相对应,这样一来,当业务逻辑复杂之后,我们可以选择性的开放接口和方法给Service进行调用。

    但是其实我们也能看到,这样其实变化并不大,而且影响也不是很大,我个人觉得可能是跟demo中的逻辑比较简单有关。而我自己也没有接触过很复杂的工作业务需求,所以也不能去判断,充血模型具体优在哪,这也需要以后在工作中不断的去尝试,思考然后才能知道了。

    但我们需要知道的是,如果在复杂的场景下,要保证框架的封装性和可拓展性,不一定要盲目的使用MVC定义的模型,可以借鉴充血模型进行思考,是否有必要进行调整。

    上面的代码只是一小部分,详细的代码demo在我的个人git中,大家可以借鉴查看

  • 相关阅读:
    Spring - BeanPostProcessors 扩展接口
    Java开发实习面试总结(49分钟)
    23~49(构造函数+继承+类的本质+ES5中的新增方法)
    解锁数据分析的神器:ChatGPT引领人工智能革命
    设计模式——模板方法模式
    动态规划矩阵连乘算法(C/C++)
    Canvas画个饼图
    【算法|动态规划No.19】leetcode413. 等差数列划分
    如何零基础自学 Python ?听我娓娓道来
    GitHub上克隆项目
  • 原文地址:https://blog.csdn.net/qq_43176812/article/details/127406035