zoukankan      html  css  js  c++  java
  • 设计模式简记-设计符合设计原则的业务系统之实现

    3.10 实战一:如何开发实现一个遵从设计原则的积分兑换系统?

    3.10.1业务开发包含的工作

    • 无外乎三方面的工作要做:接口设计、数据库设计和业务模型设计
    • 数据库和接口的设计非常重要,一旦设计好并投入使用之后,这两部分都不能轻易改动。
      • 改动数据库表结构,需要涉及数据的迁移和适配;
      • 改动接口,需要推动接口的使用者作相应的代码修改。
    3.10.1.1 积分系统的数据库设计
    • 只需要一张记录积分流水明细的表

      积分明细表 creadit_transaction
      id 明细id
      user_id 用户id
      channel_id 赚取或消费渠道
      event_id 相关事件ID,如订单id,评论id,优惠券换购交易id
      credit 积分(赚取为正,消费为负)
      create_time 积分赚取或消费时间
      expired_time 积分过期时间
    3.10.1.2 积分系统的接口设计
    • 接口设计要符合单一职责原则,粒度越小通用性就越好

    • 接口粒度太小也会带来一些问题:通讯开销;原子操作被分开,涉及分布式事务数据一致性问题。

    • 借鉴 facade(外观)设计模式,在职责单一的细粒度接口之上,再封装一层粗粒度的接口给外部使用。

      接口 参数 返回
      赚取积分 userId,channelId,eventId,credit,expiredTime 积分明细ID
      消费积分 userId,channelId,eventId,credit,expiredTime 积分明细ID
      查询积分 userId 总可用积分
      查询总积分明细 userId+分页参数 id,userId,channelId,eventId,
      credit,createTime,expiredTime
      查询赚取积分明细 userId+分页参数 id,userId,channelId,eventId,
      credit,createTime,expiredTime
      查询消费积分明细 userId+分页参数 id,userId,channelId,eventId,
      credit,createTime,expiredTime
    3.10.1.3 业务模型设计
    • 三层架构的Service

      ^: 大部分业务系统的开发都可以分为 Controller、Service、Repository 三层。Controller 层负责接口暴露,Repository 层负责数据读写,Service 层负责核心业务逻辑,也就是这里说的业务模型

    • 简单系统选择贫血开发模式

      ^: 基于贫血模型的传统开发模式和基于充血模型的 DDD 开发模式,前者是一种面向过程的编程风格,后者是一种面向对象的编程风格,无论是DDD还是OOP,高级开发模式应对复杂系统;积分系统业务相对比较简单,选择简单的基于贫血模型的传统开发模式就足够了。

    • 简单选择跟其他业务系统一块部署

    3.10.2 为什么分MVC三层开发?

    3.10.2.1 分层能起到代码复用的作用
    • 同一个 Repository 可能会被多个 Service 来调用,同一个 Service 可能会被多个 Controller 调用
    • 满足DRY原则
    3.10.2.2 分层能起到隔离变化的作用
    • 分层体现了抽象和封装:Repository 层封装了对数据库访问的操作,提供了抽象的数据访问接口

    • 基于接口而非实现编程的设计思想,Service 层使用 Repository 层提供的接口,并不关心其底层依赖的是哪种具体的数据库。方便替换数据库的时候:只需要改动 Repository 层的代码

    • Controller、Service、Repository 三层代码的稳定程度不同、引起变化的原因不同,分成三层来组织代码,能有效地隔离变化:Controller可能经常变化,分层后Controller变化并不影响其他层的稳定。

    3.10.2.3 分层能起到隔离关注点的作用
    • Repository 层只关注数据的读写。
    • Service 层只关注业务逻辑,不关注数据的来源。
    • Controller 层只关注与外界打交道,数据校验、封装、格式转换,并不关心业务逻辑。
    • 三层之间的关注点不同,分层之后,职责分明,更加符合单一职责原则,代码的内聚性更好
    3.10.2.4 分层能提高代码的可测试性
    • 代码都放到一个类中,这个类的代码会因为需求的迭代而无限膨胀。
    • 代码过多之后,可读性、可维护性就会变差。
    • 拆分代码:拆分有垂直和水平两个方向。
      • 水平方向基于业务来做拆分,就是模块化;
      • 垂直方向基于流程来做拆分,就是这里说的分层。
    • 还是那句话,不管是分层、模块化,还是 OOP、DDD,以及各种设计模式、原则和思想,都是为了应对复杂系统,应对系统的复杂性。对于简单系统来说,其实是发挥不了作用的,就是俗话说的“杀鸡焉用牛刀”。

    3.10.3 BO、VO、Entity 存在的意义是什么?

    ^ : 针对 Controller、Service、Repository 三层,每层都会定义相应的数据对象,它们分别是 VO(View Object)、BO(Business Object)、Entity,例如 UserVo、UserBo、UserEntity。在实际的开发中,VO、BO、Entity 可能存在大量的重复字段,甚至三者包含的字段完全一样。在开发的过程中,经常需要重复定义三个几乎一样的类,显然是一种重复劳动。

    3.10.3.1 更加推荐每层都定义各自的数据对象这种设计思路
    • VO、BO、Entity 并非完全一样。比如,我们可以在 UserEntity、UserBo 中定义 Password 字段,但显然不能在 UserVo 中定义 Password 字段,否则就会将用户的密码暴露出去。
    • VO、BO、Entity 三个类虽然代码重复,但功能语义不重复,从职责上讲是不一样的,是符合DRY原则的。
    • 为了尽量减少每层之间的耦合,把职责边界划分明确,每层都会维护自己的数据对象,层与层之间通过接口交互。对于非常大的项目来说,结构清晰是第一位的!
    3.10.3.2 既然 VO、BO、Entity 不能合并,那如何解决代码重复的问题呢?
    • 继承可以解决代码重复问题

      可以将公共的字段定义在父类中,让 VO、BO、Entity 都继承这个父类,各自只定义特有的字段。因为这里的继承层次很浅,也不复杂,并不会影响代码的可读性和可维护性。后期如果因为业务的需要,有些字段需要从父类移动到子类,或者从子类提取到父类,代码改起来也并不复杂。

    • 组合也可以解决代码重复的问题

      可以将公共的字段抽取到公共的类中,VO、BO、Entity 通过组合关系来复用这个类的代码。

      注意:组合可能导致数据层次问题:对象转换json会分成多个平行层次,对前端不友好

    3.10.3.3 不同分层之间的数据对象该如何互相转化?

    ​ 整个开发的过程会涉及“Entity 到 BO”和“BO 到 VO”这两种转化

    • Java 中提供了多种数据对象转化工具,比如 BeanUtils、Dozer 等,可以大大简化繁琐的对象转化工作。
    3.10.3.4 贫血模型违背封装特性如何解决?
    • VO、BO、Entity 都是基于贫血模型的,而且为了兼容框架或开发库(比如 MyBatis、Dozer、BeanUtils),我们还需要定义每个字段的 set 方法。这些都违背 OOP 的封装特性,会导致数据被随意修改。
    • Entity 和 VO 的生命周期是有限的,都仅限在本层范围内,即便设计成贫血、定义每个字段的 set 方法,相对来说也是安全的
    • Service 层包含比较多的业务逻辑代码,所以 BO 就存在被任意修改的风险了。但是,设计的问题本身就没有最优解,只有权衡。为了使用方便,我们只能做一些妥协,放弃 BO 的封装特性,由程序员自己来负责这些数据对象的不被错误使用。

    3.10.4 总结用到的设计原则和思想

    原则和思想 说明
    高内聚,松耦合 不同功能划分到不同的模块,让模块高内聚,松耦合
    单一职责原则 模块设计尽量职责单一;分层设计也是为了职责单一
    依赖注入 MVC:下层的类通过依赖注入的方式注入到上层的代码
    依赖反转原则 通过类似spring IOC这样的容器来管理对象的创建、生命周期
    基于接口而非实现编程 MVC:Service层使Respository层提供的接口,不关心底层数据库类型
    封装、抽象 分层体现了抽象和封装的思想,隔离关注点和变化
    DRY与继承、组合 VO、BO、Entity代码重复,但语义不重复,符合DRY;
    解决重复问题可用到继承和组合的方法
    面向对象设计 合适的功能放到合适的模块中,体现了面向对象设计(合适的代码放到合适的类)
  • 相关阅读:
    Hadoop OutputFormat浅析
    硬盘性能
    HDFS读文件过程分析:读取文件的Block数据
    HDFS写文件过程分析
    JAVA中的编码分析
    HBase 写入优化
    HBase ttl 验证
    大数据多维分析平台的实践
    TiDB在特来电的探索
    Druid 基础使用-操作篇(Pivot、plyql)
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12778696.html
Copyright © 2011-2022 走看看