精华内容
下载资源
问答
  • 系统功能模块设计图
    千次阅读
    2021-02-05 03:22:27

    个人财务管理系统设计

    3.1系统模块

    泛普软件中个人财务管理软件具有以下几个功能模块:个人日常收支录入模块、账目信息查看模块、用户与账目信息管理模块。个人日常收支录入模块能有效的管理用户的现金,个人财务软件可以对每天收支项目进行添加操作,账目信息查看模块使用户能够查询用户全部明细账目,某天收支情况,以及某月的金钱和物品统计信息,能够让用户对自己每月或一段时间的收支情况更直观,用户与账目信息管理模块可以让用户修改用户名密码和对错误账目信息进行修改以及删除。

    3.2总体设计

    泛普软件的个人财务管理系统分为登陆界面,个人日常收支录入,账目信息查看,用户与账目信息管理,退出系统五个模块,登陆界面需要用户名与密码进行对系统的登录,个人日常收支录入模块分为添加收入,添加支出两个模块,账目信息查看模块分为明细账目查看,分类账目查看两个模块,用户与账目信息管理模块分为修改密码与用户名,账目信息修改与删除,2个部分,退出系统一个按钮就退出整个程序,每个模块完成的功能可以从名字上明显的看出。

    个人财务软件功能结构图如下所示:

    6eec7939a4596a6d0a2f51b1c2ec2146.png

    ②. 个人日常收支录入模块

    收支录入模块是用户对每天每发生一笔现金业务进行添加操作的模块

    提供用户对现金收支信息和物品收支信息进行统计输出功能。

    ③. 账目信息查看模块

    账目信息查看模块使用户能够查询用户全部明细账目,某天收支情况,以及某月的金钱和物品统计信息,能够让用户对自己每月或每天的收支情况,更有月统计柱状图输出让用户对本月消费和支出更直观

    ④用户与账目信息管理模块

    用户与账目信息管理模块可以让用户修改用户名密码和对错误账目信息进行修改以及删除。

    3.3 个人财务管理软件数据库的ER图 收入编号

    其中用户编号是用户的主键,每个用户都有唯一的编号,其中收入编号是收入的主键,收入类型包括买入、借入,收入,如果是金钱,金额就是金钱的数量,备注就是对事件的详细说明,支出联系同收入联系,支出类型包括:借出、卖出、支出,备注可以填写用途或原因的说明。

    更多相关内容
  • 应用软件开发,就是对数据库进行增删改查操作?软件架构选型,就是选择几款流行的中间件?软件架构设计,就是把几个中间件串在一起?...本篇文章从应用软件的模块设计层面讲述软件设计的真正要求。

    应用软件开发,就是对数据库进行增删改查操作?软件架构选型,就是选择几款流行的中间件?软件架构设计,就是把几个中间件串在一起?如果真的这么认为,那么对应用系统设计可能还存在理解深度。本篇文章从应用软件的模块设计层面讲述软件设计的真正要求。

    功能模块拆分是在全面了解业务需求后,以寻找大量内聚性调用确定模块边界为目的,以寻求应用软件中易变变性和不易不变性的边界为目的应用系统设计过程。但是讨论功能模块拆分的前提是弄清楚什么是功能模块,然后才能讨论功能模块的拆分原则和设计方案

    1、功能模块的概念

    功能模块从业务层面的理解很简单,就是一个名词:如用户模块、订单模块、支付模块、合同模块;但隐含在这个名词下“功能模块”的技术意义,就至少需要具备以下特点:

    • 单一业务性:功能模块一定只处理单一业务,功能模块本身可能不能完成业务闭环,但业务闭环中关于某个业务点的处理,都应该由一个模块完成。

    • 闭合性和开放性:闭合性是指功能模块内部实现细节应该对外关闭,任何调用者不能进行修改。外部调用者要么使用要么不使用、要么直接使用要么整体替换;开放性是指业务模块本身的扩展是开放的,开发人员在不改变模块现有功能的情况下,可以对模块功能进行新增。

    • 抽象性:实际上该特点与上一个特点是紧密联系的,抽象性来源对业务需求的提取,通俗的来讲就是确定的业务边界有利于功能模块自身的扩展;例如车销订单和电商订单,虽然两种订单用于不同的业务闭环,但其业务的关键信息和对后续业务的驱动作用是一致的,所以两种类型的订单都应该归纳为一个订单模块。抽象性保证了功能模块的闭合性。

    • 接口规范性:接口规范性是指功能模块提供给外部调用者的接口调用方式、事件订阅方式是有边界的、稳定的。模块的接口可以进行规范且边界可控的原因,主要有赖于功能模块的单一业务性。如果开发人员发现功能模块提供给外部调用者的接口随时都在变化,那么说明模块的拆分存在问题。规范性同时保证了功能模块的开放性和闭合性。

    • 单向依赖的定位性:模块和外部模块的依赖一定是单向的,也就是说A模块如果直接或者间接依赖于B模块,那么B模块就一定不会“察觉”A模块的存在。由于依赖的单向性,所以模块在整个系统/子系统中一定可以找到清晰的层次定位。如果开发者发现模块无法在系统/子系统明确定位,那么说明模块的拆分存在问题。

    除了功能模块自身需具备的特点外,循环依赖问题也和功能模块的划分存在联系。那么什么叫循环依赖呢?循环依赖就是:两个或多个功能模块在接口层面出现相互依赖(包括直接和间接)的情况,例如岗位功能直接调用了用户功能的接口,用户功能在实现过程中又同时调用了岗位功能的接口(实际工作中,那些间接产生的循环调用,也会形成循环依赖),示例代码如下:

    // 岗位模块逻辑实现中依赖了用户模块的接口
    // ……
    public class PositionServiceImpl implements PositionService {
      @Autowired
      private UserService userService;
      public void doSomething() { }
    }
    // ============
    // 用户模块的逻辑实现中依赖了岗位模块的接口
    // ……
    public class UserServiceImpl implements UserService {
      @Autowired
      private PositionService positionService;
      public void doOtherthing() { }
    }
    

    循环依赖本身不是绝症,在编程技巧上来说循环依赖还可以减小功能逻辑的实现难度,提高单位时间内代码的编写效率(不需要关注设计模式的应用,只需要按照业务流程撸出代码)。但是如果将循环依赖状态和模块设计联系在一起,那么循环依赖将会对功能模块设计产生较大负面影响。

    简单来说,如果功能模块间出现循环依赖,那么功能模块就无法形成单向依赖,无法稳定在系统/子系统上的某个固定层级;另外,如果功能模块内部出现循环依赖,就代表这个功能模块无法继续向下进行更细粒度的拆分。
    在这里插入图片描述
    为什么模块间存在循环依赖就表明模块拆分失败呢?这是因为一旦存在循环依赖,将直接导致功能模块不满足单向依赖的特点要求,也就无法稳定存在于系统/子系统上的某个层级。也就是说,功能模块内部是否存在循环依赖是进行模块边界辨识的重要依据。边界以外的功能和本模块只存在标准的接口调用和事件订阅;边界以内的功能由于存在循环依赖,所以不能再继续向下进行更细粒度的拆分。如果边界以内的功能不存在循环依赖,那么说明模块还可以继续向下进行更细粒度的拆分(虽然不一定要这样做)。

    2、模块拆分原则

    2.1、高内聚性

    高内聚性用于描述模块内各功能的调用关系。高内聚性是指模块内所有接口、接口层级调用的紧密程度。这些被紧密集合在一起的工作逻辑对外是透明的,且只为一个目标而存在,就是从模块内所处不同层次出发,共同完成业务模块所负责的单一业务任务。

    例如,业务模块中的数据层只是为了完成和业务相关的数据的持久化存取存在的,不会在数据层去存取和另外业务相关的全部数据。业务模块中的业务逻辑层,只是为了完成和本业务逻辑相关的计算,其余周边业务的处理要么调用其它模块的接口完成,要么通过事件机制将自身处理情况通知出去,再由其它模块的订阅者负责完成……。
    在这里插入图片描述
    高内聚性在帮助开发人员提高开发效率的前提下,还可能导致循环依赖。循环依赖不一定完全是坏事,这要看技术团队对业务拆分的理解和要求。例如如果研发团队根据需求调研决定将用户、岗位、职位、职级归纳于一个模块,那么用户、岗位、职位、职级的逻辑关联实现就不必考虑避免四个功能需要单独形成四个模块的问题,也无需考虑循环依赖的问题(但实际情况来看这种模块粒度的设计显然太过粗放)。

    2.2、低耦合性

    耦合性用于描述模块和模块间的关联紧密程度。模块依赖的外部模块越多、需要关注的其它模块的事件越多,则模块的扩展难度越大、替换成本越高。从系统设计的角度来看,降低功能模块的耦合性比提高模块内的聚合性更为重要。这个原因很容易理解,模块内的聚合性是否紧密仅仅涉及到该模块本身设计的好坏,而模块间的低耦合性将保证模块内不好的设计所影响的范围被限制在模块内,而不会被传递到其它模块。

    2.2.1、为了达到低耦合性的要求,有几类模块间的关联方式是绝对需要避免的:

    • A、直接跨过其它模块的标准接口,对其它模块的数据进行读写:这个原因很好理解,这种修改方式将直接取决于其它模块的业务实现细节,如果其它模块的业务逻辑在内部被修改或者其他模块的实现方式直接被替换,那么本模块内的相关处理逻辑则不得不进行修改。这种处理方式也违背了面向对象设计的基本原则——依赖接口而非依赖实现。以下是一个错误示例:
      在这里插入图片描述
    • B、两个或多个模块都对同样的业务进行操作:这种场景常见于两个或者多个模块进行数据绑定的情况。例如,物流模块中货运单负责人和用户模块中人员进行绑定关联的操作场景。一个非常明显的问题是:到底应该由哪个业务模块来控制这个绑定关系?

    注意:本文一直讨论的是功能模块的设计问题,而不是用户UE交互问题。在UE层面,从方便用户操作的角度来看当然可以在用户模块提供一个直接绑定货运单的操作界面,但是在功能模块设计层面,对于绑定信息的操作当然不能设计成两个模块都可以管理绑定数据。正确的设计方式是,只能由上层模块完成绑定数据的维护(也就是归纳到上层功能模块中进行管理)。如下图所示:
    在这里插入图片描述
    这是为什么呢?首先讨论这个问题的前提是,功能模块的拆分满足要求(即上文以讨论过的功能模块应该具备的所有特性)。在这种情况下,具有这种数据绑定关系的功能模块一定具有单向依赖特点。

    将两个业务模块的绑定关系(特别是多对多关系)放置于上层模块,可以使下层模块减少关注规模、保证下层模块的稳定性,还可以增加上层模块的扩展性。例如如后续需要增加订单创建者的数据绑定关系,则无需修改用户模块,只需要增加新的订单模块。
    在这里插入图片描述
    如果设计者发现无法确定某种绑定关系不知道应该放置在哪个功能模块中,则最可能的原因是:这两个或者多个功能模块拆分失败,需要重新进行功能模块设计。或者这些关联数据是功能模块内具有内聚性的绑定信息。那么有的读者会问,如果将绑定关系交给上层模块维护,当时又需要在查询用户信息时一起关联出指定用户和物流单绑定,该怎么办呢?不要急,后续讲解如何进行模块拆分时会进行解决方式的讲解。

    2.2.2、另一种场景的模块间依赖方式,是应该尽可能减少或被限制的:

    将两个或者多个设计存在瑕疵的功能模块中存在循环依赖的部分提取出来下沉为一个诸如common-XXXX的公共功能模块。如下图所示:
    在这里插入图片描述
    这里的common模块当然是一种解决多个模块中依赖冲突的办法,但是作为下沉的功能模块,该模块存在一些问题:首先该模块一定会涉及应该由其它功能关注或操作的业务逻辑,所以该办法治标不能治本。而最根本的原因是这个办法将功能模块间的循环依赖问题迁移到这个公共模块的内部,让循环依赖以内聚性的方式继续存在,而不是解决这一系列循环依赖。

    另外,上层功能模块依赖该公共模块后,会将上层功能模块无需关注的接口、模型、事件暴露出来,增加上层模块的开发难度。最后,增加了一个无法归入任何特定功能模块的所谓公共功能模块,一定会增加业务系统本身的维护难度,而在后续的二次开发环节中,开发人员对于是否需要引入、修改这个公共模块一定会存在疑惑。

    2.2.3、好的低耦合设计将直接帮助系统设计达到一下几个效果:

    • A、更容易的二次开发实施
      这个效果用一个通俗易懂的方式进行描述,就是:好的功能模块可以在二次开发阶段由二次开发团队按照自己的需求思路和技术思路进行完全重构,且这样的二次开发是有明确边界的,这个边界应该和被二次开发替换模块的功能边界一致。二次开发团队在进行功能模块重写时,无需关注这个模块以外的模块工作原理,因为其他模块不会因为这次重写而发生改变。

    • B、功能模块层级明确
      在产品团队在进行前期产品设计或者对客户的售前工作中,经常会向团队或者目标客户出示产品的功能架构图,类似如下:
      在这里插入图片描述
      那么技术团队能否在产品/项目研发阶段真正按照这样给客户宣讲(忽悠)的功能架构图完成系统中各个功能模块的构建呢?答案是肯定的,只要按照上文提到的构建功能模块的基本原则进行系统设计,那么系统中的各个模块就可以呈现“漂亮”的顺序依赖结构。但很多时候,由于错误的功能模块拆分方式,技术团队往往无法达到产品前期设计的或者给客户承诺的功能模块拆分目标。

    • C、具有单向依赖特点和层次特点的功能模块,自身是稳定的

      这种稳定性体现在代码修改、替换的边界控制上。举个例子:当开发人员出于某些目的,需要将某个模块的实现代码删除/剪切(除暴露的接口),那么开发人员可以观察到的效果是当开发人员剪切了模块的所有代码到另外的地方,该模块本身不会报错;同样该模块原始存在的应用工程也不会报错。

      这种稳定性还体现在模块内错误的可控性——系统内的虫子被有效限制:系统研发过程中难免出现技战术问题和失误,例如边界校验问题、性能问题、需求理解问题、数值适配问题等等(bug)。但是由于好的功能模块设计的内聚性和隔离性,这些虫子活动范围只会限于各个功能模块内部。

    3、如何进行模块拆分

    那么如何进行功能模块的拆分呢?上文已经提到,功能模块拆分的原则是提搞功能模块的内聚性,降低功能模块间的耦合性。其中更重要的原则是降低模块间的耦合性,高内聚性的形成则降低模块间耦合性后的必然产物。

    3.1、基于不同业务场景,使用规范的设计模式,降低依赖:

    模块间的耦合可通过多种设计模式(主要是行为模式)进行降低(但需要注意,采用设计模式的最大原则是,同一类型问题采用相同的设计模式进行设计),最好各个模块只存在最少方法调用、最小对象传参这样的依赖方式,最小限度来说必须解决功能模块间的循环依赖问题。请看如下示例:岗位模块和用户模块由于设计问题被耦合在一起,两者存在循环依赖——这是两个坏的模块设计:

    // ……
    // 岗位模块逻辑实现中依赖了用户模块的接口
    public class PositionServiceImpl implements PositionService {
      @Autowired
      private UserService userService;
      public void doSomething() { }
    }
    // ==============
    // 用户模块的逻辑实现中依赖了岗位模块的接口
    // ……
    public class UserServiceImpl implements UserService {
      @Autowired
      private PositionService positionService;
      public void doOtherthing() { }
    }
    

    在没有解决循环依赖问题前,两个模块是分不出来业务层级的。如下图所示:
    在这里插入图片描述
    为了将两个模块进行最低限度的解耦,分出两个模块的层次,技术人员需要让两个模块的依赖关系变成单向的,如下图所示:
    在这里插入图片描述
    由于用户模块被其他功能模块调用的可能性要高于岗位模块,而且用户模块更需要进行抽象,所以一般认为用户模块应该位于岗位模块的下层(但这也不一定,决定于实际场景下的需求情况)。换句话说,岗位模块可以依赖用户模块,可以使用用户模块的SDK层(接口层)接口、模型;但是用户模块不应该依赖岗位模块,甚至不应该知晓用户模块上层的任何模块(包括岗位模块)的存在。

    从技战术的角度讲,我们只需要一些很简单的办法,就可以解决这个问题:即在用户模块提供事件通知,将用户模块工作逻辑中需要由上层模块协作完成的事件触发点公布出去,然后由上层模块按照相关需求进行实现即可。这个过程可以使用监听器模式、观察者模式等等,另外spring框架本身提供的事件订阅机制,技术人员也可以使用(这里就不再铺展开讲解了,感兴趣的读者可以参看本专题后续文章,也可以参考其他第三方资料)。示例代码如下:

    /**
     * 用户模块中定义的事件信息,注意用户模块只是定义事件,而事件的实现交由上层模块进行
     * @author yinwenjie
     */
    public interface UserEventListener<T> {
      /**
       * 当用户模块完成新的用户信息创建时,该事件会被触发
       */
      public void onUserCreated();
      /**
       * 当用户模块由于某些原因,需要知晓上层功能模块中,业务信息和指定用户(多用户)的绑定情况时,
       * 该事件会被触发 
       */
      public List<T> onUserBandingInfoRequest(String account);
    }
    

    进行依赖倒转的本质是将本模块和其他模块逻辑相关的所有实现,由下层模块迁移到上层模块。例如以上示例中,将原有用户模块中直接调用的岗位实现逻辑迁移到岗位模块内部。这样,上层业务模块的逻辑情况就对下层模块透明了。岗位功能模块可以根据自身的情况,对这些事件进行实现(订阅),示例代码如下:

    // 岗位模块逻辑实现中依赖了用户模块的接口
    public class PositionServiceImpl implements PositionService , UserEventListener<YourBusiness> {
      // 用户模块不需要知晓岗位模块的存在
      // 只需要岗位模块依赖用户模块
      private UserService userService;
      public void doSomething() {
      }
      @Override
      public void onUserCreated() {
        // ..... 
      }
      @Override
      public List<YourBusiness> onUserBandingInfoRequest(String account) {
        // 根据岗位模块中的具体逻辑进行该事件实现
        return null;
      }
    }
    

    3.2、为功能模块规划标准的调用边界

    所有上层模块对其的调用,只能通过边界进入该功能模块。为了适应各种调用场景,支持功能模块的单向依赖,并统一功能模块边界的数据描述,功能模块的调用边界至少应该包括:标准的调用接口,标准的调用接口只有处于功能模块的上级模块才能直接使用,换句换说一旦外部功能模块直接调用了本功能提供的调用接口,那么外部功能模块一定处于该功能模块的上级;标准的模型定义,标准的模型定义规范了外部功能模块向该模块传递信息的统一要求,也规范了该模块向外部功能模块返回的处理结果(这类模型一般包括枚举信息,包括VO模型或DTO模型,一般不使用Entity进行描述);标准的事件定义,为了保证该模块能向上层模块通知自身的数据变化和逻辑处理要求,功能模块必须定义进行事件定义。

    3.3、模块实现应于模块边界分离

    功能模块有了明确的功能边界后,就为功能模块建立了一堵墙将模块外部和模块内部进行隔离,并且在墙上开了一道门。门外不需要知道门内的具体逻辑实现,而门内可以有若干种具体实现。换句话说,门内的具体实现也应该和这堵墙进行分离,以便应用系统可以在构建时选择需要哪种实现,如下所示:
    在这里插入图片描述
    Spring Boot中提供的组件开发模式(注意是组件开发模式,而不是插件开发模式),可以帮助设计人员快速实现这种分离场景的要求(这里不再展开,有兴趣的读者可参看本专题后续文章)。并且任意第三方对于功能模块的调用都需要通过门进入,如下图所示:
    在这里插入图片描述

    3.4、数据耦合和参数耦合是最低的耦合形态,应该尽可能使用

    降低模块间的耦合,并不是全面去除模块间的耦合,后者的理解是不科学的也不现实。需求要求的功能,需要两个或者多个模块配合完成,这些模块间当然就一定会有耦合。脱耦的关键目标在于明确功能模块的边界,在于将“你”要处理的内容和“我”要处理的内容分割清楚,在于可以达到就算没有“你”或者换一个“你”那么“我”也可以完整处理“我”负责内容的目的。

    要达到这个目标,就需要考虑如何进行耦合,很明显直接进行接口调用或者处理逻辑调用是不行的,而使用关键数据进行模块关联(数据耦合)并利用局部参数传递数据(参数耦合),可以有效减少两个模块的耦合程度。

    用一句很好理解的话来解释:模块和另一个模块耦合时,只在本模块中记录另一个模块的关键数据而不是全部数据,尽可能少记录另一个模块在本模块中的冗余数据,这样做的目的是保证就算另一个模块的具体工作逻辑被替换掉,本模块也可以根据这些关联数据精确驱动另一个模块的处理过程;而数据的传递和驱动要求的传递,通过局部参数或者对象属性完成,这保证了另一模块的处理逻辑不会牵扯另一模块中的其他逻辑处理过程。

    3.5、文档支持

    为了便于研发团队内部进行模块开发级别的交流,也便于二次开发团队了解模块的具体作用、使用方式、注意事项,软件研发过程特别是产品级别的软件研发过程,研发团队必须使用文档进行功能模块层面的描述。

    注意,这里说的是功能模块级别的描述,而不是具体业务实现逻辑的描述过程。这两份文档的区别主要体现在对不同技术层级的描述。具体业务实现的描述可以撰写成功独立文档,更推荐直接使用规范化的代码注释进行描述;而功能模块级别的文档必须独立成文,并使用利于团队交流的知识库系统进行管理。

    功能模块级别的文档至少应该描述以下事实:该模块在整个应用系统中的位置、该模块下层(直接)依赖了哪些模块以及原因、该模块提供了暴露的调用接口和事件订阅方式、该模块的在应用系统级别的引入方式等等。

    注意,相当一部分技术人员不习惯于写文档,或者说不知道如何写文档。为了在研发团队的磨合期帮助这部分技术人员上手文档写作,研发团队可以出具一份切实可行的文档模块,将文档分为几个段落并明确每个段落的写作要点、要求和示例,帮助引导技术人员的写作思路。以下为某产品研发过程中使用的模块描述文档的模板范例:
    在这里插入图片描述

    4、实际功能模块拆分举例

    下面以一个实际例子进行举例,两者相关联的业务需求点是:创建一个新的订货信息时,需要验证订货者是否还有未完成的退货单,如果有则不允许进行订货单创建。另外退货单创建时必须有关联的订货单,且订货单的状态必须是“已完成”,退货单创建过程中必须将对应的订货单置为“失效退货”状态。

    从需求层面上看,这两个模块的功能就应该是耦合在一起的,但这里要明确的是,作为研发团队我们不可能要求客户修改需求,而业务需求间的耦合并不是技术层面的耦合,研发设计的目的就是将业务需求解耦,转变为方面维护的一个一个独立功能模块。

    在没有进行好的模块化功能设计前,本系统中的订货功能和退货功能确实也是强耦合的方式撸(码)在了一起,如下所示:

    // 订货模块有如下代码
    // 退货单服务
    @Autowired
    private ChargebackService chargebackService;
    
    @Transactional
    public void create(OrderInfo orderinfo) {
      // ......
      // 验证订货者是否有未完成的退货单
      String account = orderinfo.getAccount();
      Set<ChargebackInfo> chargebackInfos = this.chargebackService.findByAccountAndStatus(account , Status.Enable);
      Validate.isTrue(CollectionUtils.isEmpty(chargebackInfos) , "订货者还有未完成的退单,不允许新建订货单!");
      // ......
    }
    
    // =====================
    
    // 退货单模块有如下代码
    // 退货服务
    @Autowired
    private OrderInfoService orderInfoService;
    @Transactional
    public void create(ChargebackInfo chargebackInfo) {
      // ......
      // 验证退货单的订单关联信息
      String relationCode = chargebackInfo.getRelationCode();
      OrderInfo exsitOrderinfo = orderInfoService.findByCodeAndStatus(relationCode);
      Validate.notNull(exsitOrderinfo , "未发现指定的订单信息!!");
      Validate.isTrue(exsitOrderinfo.getStatus() != Status.DONE  , "指定订单还未完成处理,不能进行退货!");
      
      // ...... 继续做退货单的其他处理
      // 然后在退货单模块,直接调用订单模块的接口,修改订货单状态
      this.orderInfoService.updateStatus(relationCode , Status.DONE);
      // ......
    }
    

    以上的示例代码是开发人员在实际系统开发过程中,编写的再简单不过的业务代码了。从需求的角度看这段代码没有问题,可以这样理解以上业务代码:就是开发人员按照需求人员对需求的描述,直接翻译成代码“贴”到应用系统中。从编码规范来看以上代码也没有问题:使用统一的命名规范、格式规范,有统一的边界校验控制,甚至使用统一的工具和编写技巧尽可能减少代码规模(这里特别说明一下,一些开发人员喜欢在开发过程中编写许多通用工具,例如字符串处理工具、日期处理工具、数值计算工具,并设想开发团队中的其他开发人员会使用这些工具,形成所谓的规范。这种做法是不科学、有危害的,原因会在本专题的后续文章中进行说明)。

    但是,以上代码从系统设计的角度看就存在问题了:订货单模块和退货单模块出现了强依赖,两个模块被循环依赖在了一起。如果产品团队根据需求分析最终决定订货模块和退货模块就应该是一个模块,那么这样做当然也没有什么大问题(就是后续要拆分成更细粒度的模块,会耗费大量工作),因为按照本文所述循环依赖只能存在于功能模块以内,如果循环依赖出现在模块间那么就证明模块拆分失败。

    在这里插入图片描述
    但是在本示例中,退货模块和订货模块显然属于两个需要独立工作的模块,那么必须通过系统设计的方式,降低这两个模块的耦合性,至少需要将两个模块的依赖方式变成单向依赖,将两个模块的耦合度降低到只有数据耦合和参数耦合。

    在进行设计前,我们先来确定一下这两个模块更科学的依赖方向:显然订货模块放置到更下层,可以使系统的依赖关系更科学,因为按照业务需求订货模块还将被除了退货模块以外的多个模块所依赖。如下图所示:
    在这里插入图片描述
    最终我们确认的模块拆分方案是:订单模块不应该引入任何退货模块的接口,甚至订单模块就不应该知道有一个退货模块。那么如何去掉订单模块中关于相关退货逻辑的处理呢?如何反转订单所依赖的退货单接口?

    设计模式中多种行为模式可以解决这个问题,最简单的方式就是为订单模块设计规范的事件接口,然后由上层模块根据自身业务需求实现这些事件(监听器模式/观察者模式)。我们先为订单模块定义标准的事件:

    /**
     * 订单事件,这个事件的定义在订单模块中
     * @author yinwenjie
     */
    public interface OrderEventListener {
      /**
       * 当订单被创建时(但本地事务还没有提交前),该事件被触发
       * @param orderInfo 本次新建的订单信息,通过参数方式进行传入
       */
      public void onCreated(OrderInfo orderInfo);
    }
    

    但是订单模块并不负责实现这些事件。接着,订单在自身的创建动作完成后,进行事件的触发,代码如下所示:

    // 订单模块的代码如下
    /**
     * 订单事件监听,之所以是集合,是因为可能有多个监听器的实现
     */
    @Autowired(required = false)
    private List<OrderEventListener> orderEventListeners;
    @Transactional
    public void create(OrderInfo orderinfo) {
      // ......
      // 在订单边界校验、自身处理过程完成后,触发事件
      if(!CollectionUtils.isEmpty(this.orderEventListeners)) {
        this.orderEventListeners.forEach(item -> item.onCreated(orderinfo));
      }
      // ......
    }
    

    这样的设计也基本能满足上文提到的进行模块设计的规范要求,特别是进行两个模块耦合的要求:首先采用监听器模式解决两个模块的循环依赖问题;然后退货单无论是直接调用订单的接口,还是实现订货模块的事件订阅都遵循订单模块向外暴露的标准边界(门),完全避免了和订单模块内的任何具体实现逻辑产生关系;最后退货模块和订单模块的耦合仅限于调用方法时传递的参数(事件中传递了订单对象信息),且退货单模块仅关联订单模块中的订单业务编号(在退货单模块中,该属性称为“第三方业务单据relationCode”),关联的目的是帮助订单模块的处理过程精确定位到相关的单据信息。

    这样一来,订单模块只需要将自身发生的变动的情况或者需要获取数据的事件发布出去,无需知道有哪些模块会订阅这些事件(订单模块除了数据层面、参数层面和各个上层模块有耦合以外,订单模块压根不知道有哪些上层模块会订阅事件,更谈不上知晓这些模块的作用)。
    在这里插入图片描述
    关键代码如下所示:

    // 此段代码是模块改造后,退货模块的代码示例
    // 该服务实现了订单模块的OrderEventListener监听接口
    public class ChargebackServiceImpl implements ChargebackService ,  OrderEventListener {
      
      @Override
      public void onCreated(OrderInfo orderInfo) {
        // 之前退货单放置在订单模块的代码放到了这里
        // 具体来说就是,验证订货者是否有未完成的退货单
        String account = orderinfo.getAccount();
        Set<ChargebackInfo> chargebackInfos = this.findByAccountAndStatus(account , Status.Enable);
        Validate.isTrue(CollectionUtils.isEmpty(chargebackInfos) , "订货者还有未完成的退单,不允许新建订货单!");
        // ...... 其它处理逻辑过程
      }
    }
    

    这是最简单的一种设计模式的应用方式。在这里如何进行事件的发布或者如何进行实现者行为的控制,完全取决于技术人员对需求的抽象能力,以及将抽象需求转换为设计思路的能力。再例如,当事件发生时系统中会有多个实现,但是只能按照条件选择一个最合适的实现进行调用,那么可以使用策略模式进行设计,如下所示:

    
    /**
     * 订单事件处理策略定义
     * @author yinwenjie
     */
    public interface OrderCreateEventStrategy {
      /**
       * 该方法将在订单创建事件发生后,首先被触发,
       * 系统将根据该方法的返回情况,确定是否使用该策略匹配本次订单创建后的处理逻辑
       * @param orderInfo 本次进行创建的订单
       * @return 如果返回true,则表示该处理策略逻辑将被正式执行;其他值,不执行该策略实现逻辑
       */
      public boolean isHandler(OrderInfo orderInfo);
      /**
       * 只有当本策略实现的isHandler方法返回true,该方法才会执行
       * @param orderInfo 本次进行创建的订单
       */
      public void onCeated(OrderInfo orderInfo);
    }
    

    接着本文再举一个例子:如果需要将事件的实现行为串起来执行,且需要按照业务逻辑对执行顺序进行管理,那么可以使用责任链模式进行设计(注意,责任链模式建议使用递归而非循序进行控制,最好准备责任链的上下文管理器[完全可以参考Servlet中filter的设计思路])。关键接口示例如下:

    /**
     * 订单模块为了事件处理,定义的责任链抽象类。
     * 事件策略逻辑过滤
     */
    public abstract class OrderEventFilter {
      /**
       * 该方法将在订单事件触发时,参与逻辑处理链
       * @param orderInfo 当前发生事件的订单
       * @param event 事件类型,包括DELETE,CREATE,UPDATE ......
       * @param orderEventHolder 订单事件管理器,是否进行后续处理或处理过程的上下文,由该对象控制
       */
      public abstract void handler(OrderInfo orderInfo , Event event , OrderEventHolder orderEventHolder);
    }
    

    有的读者会问,这些原则和示例是否只适用于单应用系统,如果应用系统是微服务架构又该怎么办呢?微服务架构同样需要遵从功能模块设计的原则,实际上本文的内容已经足可以帮助读者扩展出微服务系统下的模块构建方式。不过微服务系统由于涉及进程间通信,所以需要增加在另一些关键技术方案上的突破,例如如何保证多进程间的数据一致性(传统的基于数据库的分布式事务一定是不行的)、再例如怎么控制进程间的消息订阅和发布等等。这些坑在本专题的后续内容中将逐步填上。

    另外,本专题后续文章也会逐渐讨论与二次开发相关的实施方案,包括但不限于如何在功能模块内部完成开发(这种场景经常出现在以项目驱动的产品研发过程中),如何替换功能模块,如何将单一化应用系统改造为微服务系统等等。

    展开全文
  • 毕业设计——如何画系统功能结构

    万次阅读 多人点赞 2020-12-08 20:37:41
    一般软件专业的学生用到工具都是starUML画各种类图、流程、时序、活动、部署、用例…画图,但是系统功能架构还是推荐用Visio工具绘制。因为 一、打开Visio 找到组织结构 二、空缺带 依次将功能...


    前言

    一般软件专业的学生用到工具都是starUML画各种类图、流程图、时序图、活动图、部署图、用例图…画图,但是系统功能架构图还是推荐用Visio工具绘制。因为


    一、打开Visio

    找到组织结构图
    在这里插入图片描述

    二、空缺带

    在这里插入图片描述

    在这里插入图片描述

    依次将功能连接起来即可。
    在这里插入图片描述

    三、或者选择Processon官网在线制图。

    官网地址:https://www.processon.com/
    在这里插入图片描述
    注册进入后可以看到侧边栏有流程图、UML
    在这里插入图片描述

    一目了然,非常方便。


    贫穷是不需要计划的,致富才需要一个周密的计划——并去实践它。

    展开全文
  • 用Visio画软件(模块功能图

    千次阅读 多人点赞 2022-05-02 17:06:02
    从左边的“框”拖动到右边的幕布上,自己调整大小,双击编辑文字,选中可以在上方功能栏选择“设计”,改变框的颜色和样式 下滑“方块”栏,找到“多树枝直角”,并拖动到右侧 白色的圈圈可以调整它的位置方向...

    最开始自己也不会画,画了好久也没画出自己心中所想的,比如下图这样的:

    在形状框中搜索“方块”,其中的图形有“框”和“多树枝直角”这两个组件

    从左边的“框”拖动到右边的幕布上,自己调整大小,双击编辑文字,选中可以在上方功能栏选择“设计”,改变框的颜色和样式

    下滑“方块”栏,找到“多树枝直角”,并拖动到右侧

     白色的圈圈可以调整它的位置方向和高度,调整后如下:

     黄色的圈圈可以链接子功能框,两个就代表可以链接两个子功能框,若两个不够,可以从中间的黄色圈圈再拖出来一个,拖动黄色圈圈也可以调整子功能框的位置

    一个小技巧:图框不用一次画那么多,先画出一个并填充好文字,其余的只要按住Ctrl同时拖拽图框就可以复制出任意个相同图框,后续只要改改文字调调位置连上线就OK了。

     再下层的的子模块操作和上面步骤一样,一步步操作就好,然后我写的最后结果如下:

     因为找了蛮久没有看到比较详细的教程,所以就写了这篇文章,给像我有一样困扰的初学者提供一下参考,欢迎有更好的方法一起交流!

    展开全文
  • 概要设计功能模块

    千次阅读 2021-10-17 19:53:56
    功能模块描述 所谓功能模块,从字面上理解,就是以功能来进行划分模块。 接着,根据功能特性多少,决定是否要划分“子功能模块”。 这里就容易出现一个问题,如何去确定每个功能的界限呢,以及很多人会拿用户角色来...
  • 这周周一,我们导师要求小组成员开会,我们分别汇报自己的工作,在会中,谈到了用例,于是我们开始对大家熟悉的用例进行探讨。经过探讨与自己的思考,我认为应该从以下几个问题来弄清楚用例的作用。 1、用例...
  • 1、什么是模块模块可以认为是组成系统的基本单位,它具有可组合、分解、更换的特点。系统中任何一个处理功能都能看成是一个模块模块根据具体化的程度可划分为...
  • 4.2系统主要功能模块设计: (1)基本信息模块:是管理员对学生信息进行添加操作。通过本界面管理员可以对学生信息进行相应的修改,包括学号、姓名、出生日期等。可以通过该界面输入学生姓名实现查询该学生的所有...
  • 为了方便中小型企业想拥有自己的商城而设计,他不单单是一个网上商城,还是一个强大的线下商品进销存管理系统。 二、功能模块 1、用户功能模块 为了更好的对系统进行管理和维护,设定了不同权限的用户来管理商城...
  • 我们在进行系统架构设计时,往往将一个系统分解成若干个子系统,每个子系统又分解为若干个程序模块,分解后的子系统和程序模块都会执行一些相对独立的功能,在这里子系统也可以看作是较大的程序模块。分解后的这些子...
  • 模块图】软件-系统架构-模块图

    千次阅读 2017-02-15 19:25:00
    上遍文章介绍了系统架构-技术,今天来简单说下系统模块,基于系统平台分四大模块: 一、客户平台模块 提供客户充值、充流量包、办理宽带、积分商城、积分记录等功能 二、代理商模块 提供推荐码、提现、返佣记录...
  • 二、功能需求与模块设计

    千次阅读 2019-09-05 14:15:35
    功能需求与模块设计 功能需求 本项目主要实现信息管理系统中的用户与权限管理的通用模块,功能要求如下: (1)用户管理:实现用户的增删改查和用户的角色分配。 (2)权限管理:实现权限的增删改查。 (3)...
  • 分析与设计:员工管理系统

    千次阅读 2022-04-20 18:40:40
    2.1系统功能需求分析 2.1.1功能分析 2.1.2系统用例分析 2.2系统非功能需求分析 2.2.1 运行需求分析 2.2.2性能需求分析 2.2.3 界面需求分析 2.2.4其他需求分析 3.系统设计 3.1 系统总体设计 3.1.1 需求规定...
  • 系统功能结构

    万次阅读 2013-07-06 18:56:02
    1、系统前台功能结构 2、系统后台功能结构
  • 2021年的5月份,那时的我大二,受某个学校的老师之托,帮助学校的某个学院做一个系统《创新项目申报管理支持系统》,功能是管理学校的双创项目,能实现项目从申报到结题等等一系列的操作。团队成员一共4个,2个后端+...
  • 一、商城架构设计 现以一个小型的商城来设计访问实现系统,后期会根据业务的增长和用户的增加...三、系统功能列表 画出商城功能树的原因是为了更好更方便的看出包含的功能有那些,系统完成开发后的主功能列表。 ...
  • 实验一 结构化分析与设计——过程与工具 1、实验目的 ...深入体会数据流、数据字典、业务流程、E-R系统功能结构、程序流程在软件开发中的作用。 (2)利用软件绘制数据流、业务流程.
  • 浅谈MES系统质量管理功能模块

    万次阅读 2021-01-27 13:35:46
    MES系统质量管理模块,完成了制造业务和质量管控全过程的自然融合,保证了质量活动与制造全过程的完美交互,制造全过程中全部静态和动态的数据信息在系统中,伴随着制造业务的深入开展,顺理成章地从各个环节被自动...
  • 21、系统模块管理的设计

    千次阅读 2018-07-06 18:31:11
    系统模块设计系统中有关联的部分组合在一起,构成具有特定功能的子系统。划分模块的内部组成具有较强的耦合性,模块本身具有一定的通用性。不同的模块间可以进行相互组合与依赖,进而构成不同的产品。模块设计...
  • Java学生管理系统功能结构

    千次阅读 2019-06-17 08:51:20
    系统包括登录注册、系统设置(设置学校信息、设置状态栏信息、修改用户密码)、数据操作(增加学生记录、编辑学生记录、删除学生记录、浏览学生记录)、查询学生(按学号查询、按姓名查询、按班级查询、按系部查询...
  • 学生选课系统描述:  学生选课系统是一个面向管理员...本文将着重介绍教师的功能模块设计。   语言和环境  A、实现语言:Java  B、实现技术: JavaSE、JDBC、Swing  C、环境要求: NetBeans 8.0.2, SQLServ
  • 架构功能图

    千次阅读 2020-09-17 22:10:02
    支付系统功能架构 支付业务的基础系统的复杂性和稳定性是支付业务是否能够及时安全处理的根本,该支付系统功能架构收集了支付宝的系统架构。完整的支付系统整体架构! 从产品分类、模块功能和业务流程,了解支付...
  • 数据库应用系统功能设计与实施

    千次阅读 2019-08-15 15:58:46
    ps:该转自https://blog.csdn.net/DaGongJiGuoMaLu09/article/details/88617753 详细知识: 第4章软件体系结构与设计过程...1)构件是组成系统的具有一定独立功能的不同粒度的程序模块、独立程序或 2)连接件:将不...
  • 这周周一,我们导师要求小组成员开会,我们分别汇报自己的工作,在会中,谈到了用例,于是我们开始对大家熟悉的用例进行探讨。经过探讨与自己的思考,我认为应该从以下几个问题来弄清楚用例的作用。 1、用例...
  • 正如我们将要看到的那样,Python鼓励模块化的程序结构,将功能相近的可重用单元组织在一个模块中,这种方式符合直觉,同时也合乎直觉。在这个过程中,我们也会探索Python模块、导入以及对象属性这三个核心概念。 ...
  • 文章目录一、APB_SPI项目介绍1.1.SPI IP核1.2....寄存器模块设计2.4.APB接口模块设计 一、APB_SPI项目介绍 1.1.SPI IP核   数据接收和发送是SPI IP 核最基本的功能。嵌入式CPU通过系统总线对SPI IP核内部模块进行...
  • 数据治理管理平台功能模块与特性

    千次阅读 2022-03-21 14:05:08
     标准的数据治理元数据管理系统,应满足以下四大功能模块与特性:  元数据采集:能准确便捷地从各类数据库及大数据平台中采集全生命周期的各类元数据,包括各类数据实体机加工逻辑,并支持异构环境;  
  • 敏捷开发一般不推荐详细设计系统总体设计、概要设计可合并为架构(Architecture)设计。 一、What is UML? 二、Why UML Modeling? 三、14种UML类型概述 常用的 UML 可分静(态)、动(态)两类...
  • 本文首先对目前家政管理系统开发所需技术进行调研,再结合当前家政行业发展的趋势,对该系统提出可行性分析,设计开发出了系统具体功能需求。系统设计采用 B/S 三层模式架构,以JAVA语言工具和MySQL数据库;以及HTML...
  • 储户填写的存款单或取款单由业务员键入系统,如果是存款,系统记录存款人姓名、住址、存款类型、存款日期、利率等信息,并印出存款单给储户﹔如果是取款,系统计算利息并印出利息清单给储户。 要求 主要包括以下两...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 476,159
精华内容 190,463
关键字:

系统功能模块设计图