精华内容
下载资源
问答
  • 4.2系统主要功能模块设计: (1)基本信息模块:是管理员对学生信息进行添加操作。通过本界面管理员可以对学生信息进行相应的修改,包括学号、姓名、出生日期等。可以通过该界面输入学生姓名实现查询该学生的所有...
    

    4.2系统主要功能模块设计:

    1)基本信息模块:是管理员对学生信息进行添加操作。通过本界面管理员可以对学生信息进行相应的修改,包括学号、姓名、出生日期等。可以通过该界面输入学生姓名实现查询该学生的所有信息。管理员可以把学生的整条信息进行删除,同时也删除学生的成绩。


    2)成绩管理模块:是管理员对学生综合成绩进行管理。包括姓名、学号、课程、课程成绩等信息录入,查询,和修改操作。

    3)课程管理模块:在该模块可以实现管理员对某个系或班级的课程信息按照学分、课程类型、课程编号、以及学时等条件录入,修改,删除操作。根据课程名称进行查询。

    4.4数据库设计:


    4.4.1数据库概念结构设计

    (1)学生E-R图:


    (2)课程E-R图:

    (3)成绩E-R图:












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

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

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

    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);
    }
    

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

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

    展开全文
  • 订单功能模块设计与实现

    万次阅读 多人点赞 2018-03-08 16:33:13
    https://www.cnblogs.com/fengli9998/p/6417117.html在商城项目中,之前我们介绍了购物车功能模块的实现,商品加入到购物车之后,就是到购物车结算,然后显示购物车的商品列表,点击去结算,然后到了未提交前的订单...

    https://www.cnblogs.com/fengli9998/p/6417117.html


    在商城项目中,之前我们介绍了购物车功能模块的实现,商品加入到购物车之后,就是到购物车结算,然后显示购物车的商品列表,点击去结算,然后到了未提交前的订单列表

    点击提交订单后,生成此订单,返回订单的订单号,付款金额,订单预计到达时间。订单系统是一个非常重要的系统,我们的移动端、PC端都需要订单系统,所以这里我们将订单系统单独作为一个服务来,留出接口供客户单来调用

     

    今天我们来看下这个订单系统到底是如何实现的:

    一、订单系统功能

    订单系统主要包含哪些功能模块呢?

    创建订单功能、查看订单列表、根据订单id查询订单的详细信息、订单修改、订单取消、订单状态、订单评价等功能的实现。

    今天我们来看下创建订单的流程:

    二、订单系统的数据库表的设计

    创建订单说到底就是向订单表中添加数据,即insert这些信息。

    下单功能一定要使用关系型数据库表,保证数据的一致性,因为创建订单要保证在一个事务(一个事务就是指向数据库中进行的一种操作:比如插入,删除等等)里面,nosql数据库不支持事务,可能会丢失数据。

    我们在网上购物的时候通常这个订单包含的信息比较多,所以对于订单系统如何创建它的数据库也是非常重要的。创建数据库遵循数据库设计的三大范式原则来设计。

    我们创建了三个表:tb_order(订单信息表),tb_order_item(订单详情表),tb_order_shipping(订单配送表).

    tb_order:这里包含了订单的基本信息

      

    tb_order_item:订单详情表:订单的详情主要就是购买商品的信息,通过订单的id来实现关联

    tb_order_shipping:订单配送表:

    这是三个基本的表,其实还可以有物流信息表,订单交易信息表。这里我们采用这三张表足够。

    三、订单系统接口文档,一般我们开发的时候会收到已经写好的接口文档,比如创建订单的接口文档。

    从上面这个表中,我们可以看到该接口的url,接口的传入参数和返回值。

    接下来我们针对这三个来进行代码的编写:

    url属于controller层,

    传入参数这里我们可以看到是数据库建立的三张表信息:第一个是tb_order,第二个是一个集合式的订单明细List,第三个是订单的配送信息表。

    所以传入参数就是这三个对象。这里我们是编写接口,供客户端调用,至于客户端怎么将这些参数传递过来,那是客户端团队考虑的事情。

    返回值这里使用了taotaoresult来包装了下,因为我们提交订单成功后,返回的是订单号,即订单的id所以,我们需要向客户端传递订单id过去,并显示在订单创建成功的页面。

    下面看下订单服务接口的service层的实现:

    service层的主要实现是将订单信息添加到数据库中,即接收controller传递过来的对象,然后补全页面没有的字段,insert数据库,这里可以使用逆向工程生成的dao。

    另外还有个问题:

    订单编号:订单编号用什么形式比较好呢?

     

    解决方案一(不能使用):

     

    使用mysql的自增长。

     

    优点:不需要我们自己生成订单号,mysql会自动生成。

     

    缺点:如果订单表数量太大时需要分库分表,此时订单号会重复。如果数据备份后再恢复,订单号会变。

     

    方案二:日期+随机数

     

    采用毫秒+随机数。

     

    缺点:仍然有重复的可能。不建议采用此方案。在没有更好的解决方案之前可以使用。

     

    方案三:使用UUID

     

    优点:不会重复。

     

    缺点:长。可读性查。不建议使用。 

     

    方案四:可读性好,不能太长。一般订单都是全数字的。可以使用redis的incr命令生成订单号。

     

    优点:可读性好,不会重复

     

    缺点:需要搭建redis服务器。

    所以我们选取方案四作为生成订单号的方案。

    那么service层的编码如下:

    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
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    package com.taotao.order.service.impl;
     
    import java.util.Date;
    import java.util.List;
     
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
     
    import com.taotao.common.utils.TaotaoResult;
    import com.taotao.mapper.TbOrderItemMapper;
    import com.taotao.mapper.TbOrderMapper;
    import com.taotao.mapper.TbOrderShippingMapper;
    import com.taotao.order.dao.JedisClient;
    import com.taotao.order.service.OrderService;
    import com.taotao.pojo.TbOrder;
    import com.taotao.pojo.TbOrderItem;
    import com.taotao.pojo.TbOrderShipping;
    //订单模块实现
    @Service
    public class OrderServiceImpl implements OrderService {
        @Autowired
        private TbOrderMapper orderMapper;
        @Autowired
        private TbOrderItemMapper orderItemMapper;
        @Autowired
        private TbOrderShippingMapper shippingMapper;
        @Autowired
        private JedisClient jedisClient;
        @Value("${ORDER_GEN_KEY}")
        private String ORDER_GEN_KEY;
        @Value("${ORDER_DEFAULT_KEY}")
        private String ORDER_DEFAULT_KEY;
        @Value("${ORDER_ITEM_KEY}")
        private String ORDER_ITEM_KEY;
         
    //创建订单功能实现
        @Override
        public TaotaoResult createOrder(TbOrder order,List<TbOrderItem> orderItem, TbOrderShipping orderShipping) {
            //逻辑步骤:创建订单即向数据库中三个表中插入数据,所以讲数据库中的字段补全即可。页面传递过来的就不需要补全了,直接可以写入数据库
            /*第一步:订单号的生成方式:因为订单号的特殊性,订单后最好采用数字组成,且不能重复,可以采用redis中自增长的方式来实现,
             * 在redis中,如果无key,则redis自动创建你写的key的名字。采用incr的命令来实现订单号的自增。默认自增是从1开始的,因为考虑到用户的原因,在redis中设置一个默认的key值
             * 这样用户体验会好些,不会因为看到简单的1,2,3,所以设置一个默认的key值
            */
            String string = jedisClient.get(ORDER_GEN_KEY);
            if (StringUtils.isBlank(string)) {
                //如果redis中的key为空,则利用默认值
                String defaultKey = jedisClient.set(ORDER_GEN_KEY,ORDER_DEFAULT_KEY);  
            }
            //如果存在此key则,采用incr命令自增
            long orderId= jedisClient.incr(ORDER_GEN_KEY);
            //然后向订单表中插入数据
            order.setOrderId(orderId+"");
            //订单状态:状态:1、未付款,2、已付款,3、未发货,4、已发货
            order.setStatus(1);
            order.setCreateTime(new Date());
            order.setUpdateTime(new Date());
            order.setBuyerRate(0);
            //补全完信息后,插入数据库表
            orderMapper.insert(order);
            //补全完订单表后,将订单明细表补全,因为之前订单写入redis,订单明细的id也写入redis吧,自动创建这个id的key。这个不需要默认编号了。对比页面传递的参数,将剩下的补全
           String string2 = jedisClient.get(ORDER_ITEM_KEY);
           long orderItemId = jedisClient.incr(string2);
           //因为传递过来的是一个集合列表,所以遍历列表
           for (TbOrderItem tbOrderItem : orderItem) {
               tbOrderItem.setId(orderItemId+"");
               tbOrderItem.setOrderId(orderId+""); 
               orderItemMapper.insert( tbOrderItem);
        
            //接下来补全订单配送表
            orderShipping.setOrderId(orderId+"");
            orderShipping.setCreated(new Date());
            orderShipping.setUpdated(new Date());
            shippingMapper.insert(orderShipping);
             
            return TaotaoResult.ok(orderId);
        }
     
    }

     controller:层实现

    controller需要将对象传递给service层:(客户端向服务器端传入的参数格式,详见后面博文)

    我们看到接口文档中,controller接收的参数是一个json格式的字符串,也就是说客户端传递过来的是json格式的字符串。

    这就涉及到springMVC是如何接收json字符串的,需要用到@RequestBody注解。这里多说几句:

    @ResponseBody注解的原理是response只能响应一个字符串,当我们的返回值是java对象的时候,它有一个默认行为,即利用jackson包将java对象转为字符串响应。这是一个默认自动的行为,不需要我们设置,只要这个注解即可。

    @RequestBody注解同理:利用这个注解告诉springMVC我们现在接收的是一个json字符串,需要采取默认行为利用jackson包将json字符串转换为java对象,所以controller层我们需要一个java对象的pojo。

    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
    package com.taotao.order.pojo;
     
    import java.util.List;
     
    import com.taotao.pojo.TbOrder;
    import com.taotao.pojo.TbOrderItem;
    import com.taotao.pojo.TbOrderShipping;
     
    public class Order extends TbOrder {
        private List<TbOrderItem> orderItems;
        private TbOrderShipping orderShipping;
        public List<TbOrderItem> getOrderItems() {
            return orderItems;
        }
        public void setOrderItems(List<TbOrderItem> orderItems) {
            this.orderItems = orderItems;
        }
        public TbOrderShipping getOrderShipping() {
            return orderShipping;
        }
        public void setOrderShipping(TbOrderShipping orderShipping) {
            this.orderShipping = orderShipping;
        }
     
    }

      controller层实现:

    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
    package com.taotao.order.controller;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
     
    import com.taotao.common.utils.ExceptionUtil;
    import com.taotao.common.utils.TaotaoResult;
    import com.taotao.order.pojo.Order;
    import com.taotao.order.service.OrderService;
    //订单管理模块
    @Controller
    @RequestMapping("/order")
    public class OrderController {
        @Autowired
        private OrderService orderService;
        @RequestMapping("/create")
        @ResponseBody
        public TaotaoResult createOrder(@RequestBody Order order ){//创建订单模块实现
            try {
                TaotaoResult taotaoResult = orderService.createOrder(order ,order.getOrderItems(), order.getOrderShipping());
                return taotaoResult;
            catch (Exception e) {
                e.printStackTrace();
                return TaotaoResult.build(500,ExceptionUtil.getStackTrace(e));
            }
         
        }
     
    }

      以上代码是订单服务接口的创建订单接口代码实现。

    接下来我们看下客户端如何调用订单服务层的:

    客户端当我们点击去购物车结算的时候,显示购物车列表。

    在购物车列表页面,当点击去结算的时候,跳转到未提交订单的页面。

    点击提交订单,跳转到显示成功页面。

    controller层实现:

    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 com.taotao.portal.controller;
     
    import java.util.List;
     
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.joda.time.DateTime;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
     
    import com.taotao.portal.pojo.CartItem;
    import com.taotao.portal.pojo.Order;
    import com.taotao.portal.service.CartService;
    import com.taotao.portal.service.OrderService;
     
    @Controller
    @RequestMapping("/order")
    public class OrderController {
        @Autowired
        private CartService cartService;
        @Autowired
        private OrderService orderService;
        @RequestMapping("/order-cart")
        //点击去结算,显示订单的页面
        public String showOrderPage(HttpServletRequest request,HttpServletResponse response,Model model){
            List<CartItem> list= cartService.showCartList(request);
            model.addAttribute("cartList", list);
            return "order-cart";   
        }
        //点击提交订单,显示订单号,订单金额,预计送达时间
        @RequestMapping("/create")
        public String showSuccessOrder(Order order,Model model){
         try {
     
            String orderId= orderService.createOrder(order);
            model.addAttribute("orderId ",orderId);
            model.addAttribute("payment",order.getPayment());
            model.addAttribute("date"new DateTime().plusDays(3).toString("yyyy-mm-dd"));
            return "success";
         
        catch (Exception e) {
        e.printStackTrace();
        model.addAttribute("message""创建订单出错");
        return "error/exception";
       }
        }
    }

      service层实现:

    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
    package com.taotao.portal.service.impl;
     
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
     
    import com.taotao.common.utils.HttpClientUtil;
    import com.taotao.common.utils.JsonUtils;
    import com.taotao.common.utils.TaotaoResult;
    import com.taotao.portal.pojo.Order;
    import com.taotao.portal.service.OrderService;
    @Service
    public class OrderServiceImpl implements OrderService {
        @Value("${ORDER_BASE_URL}")
        private String ORDER_BASE_URL;
        @Value("${ORDER_CREATE_URL}")
        private String ORDER_CREATE_URL;
         
    //创建订单的客户端实现
        @Override
        public String createOrder(Order order) {
        //因为httpclientUtil中dopost传递的是json格式的数据,所以需要将order这个java对象转换成Json格式 
            String json = HttpClientUtil.doPost(ORDER_BASE_URL+ORDER_CREATE_URL+JsonUtils.objectToJson(order));
            //为了获取里面的订单id,我们需要将获得的字符串转换为taotaoresult,然后获取数据,因为服务层传递过来的就是订单的id,所以获得数据也是获得的id
            TaotaoResult result= TaotaoResult.format(json);
            if (result.getStatus()==200) {
                Object orderId = result.getData();
                return orderId.toString();
            }
            return "";
        }
     
    }

      这里同样用了pojo类Order类。

    拦截器的问题:因为提交订单我们需要用户登录,所以在springMVC.xml文件中配置上:

     

    展开全文
  • 学生选课系统描述:  学生选课系统是一个面向管理员...本文将着重介绍教师的功能模块设计。   语言和环境  A、实现语言:Java  B、实现技术: JavaSE、JDBC、Swing  C、环境要求: NetBeans 8.0.2, SQLServ

    学生选课系统描述:
      学生选课系统是一个面向管理员、教师、学生三个对象的小型选课系统,实现了管理员对教师、学生、课程等管理,教师任课信息查询和学生选课,以及修改密码等功能。本文将着重介绍教师的功能模块设计。
      
    语言和环境
      A、实现语言:Java
      B、实现技术: JavaSE、JDBC、Swing
      C、环境要求: NetBeans 8.0.2, SQLServer2008、JDK1.7

    系统业务流程(教师、学生):
    教师业务流程

    1、教师修改密码模块
    实现类:TeaUpdatePwdFrame
    成员变量:
    教师修改密码模块
    成员方法:
    a) public TeaUpdatePwdFrame()
    构造函数,初始化界面
    b) private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
    点击修改按钮,触发修改密码事件
    c) private void jButton2ActionPerformed(java.awt.event.ActionEvent evt)
    点击取消按钮,触发清空文本事件
    d) private void formInternalFrameClosed(javax.swing.event.InternalFrameEvent evt)
    关闭该内部窗口

    2、教师任课信息模块
    实现类:TeaSearchTeachFrame
    成员变量:
    任课信息
    成员方法:
    a) public TeaSearchTeachFrame()
    构造函数,初始化窗体
    b) private void formInternalFrameClosed(javax.swing.event.InternalFrameEvent evt)
    关闭该窗口
    c) private void initteach()
    初始化教师信息
    d) private void showOnTable(List<Course> list)
    将数据载入表中

    3、(教师所任课程的)选课信息模块
    实现类:TeaSearchSelectFrame
    成员变量:
    课程选课信息
    成员方法:
    a) public TeaSearchSelectFrame()
    初始化窗体及控件,并将数据填充进课程编号组合框里
    b) private void formInternalFrameClosed(javax.swing.event.InternalFrameEvent evt)
    关闭该内部窗体
    c) private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
    点击搜索按钮出发事件进行查询该课程的学生信息
    d) private void jToggleButton1ActionPerformed(java.awt.event.ActionEvent evt)
    点击导出文件按钮出发事件进行
    e) private void initcourse()
    对课程编号组合框的内容进行初始化
    f) private void showOnTable(List<StudentView> list)
    将数据填入表中

    4、成绩管理模块
    实现类:TeaScoreFrame
    成员变量:
    成绩管理
    成员方法:
    a) public TeaScoreFrame()
    构造函数,初始化窗体和控件
    b) private void initCourse()
    初始化课程编号组合框
    c) private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
    点击搜索按钮触发事件搜索该课程的学生成绩信息
    d) private void jButton2ActionPerformed(java.awt.event.ActionEvent evt)
    点击导出文件按钮触发事件导出学生成绩信息表
    e) private void jTable1MouseClicked(java.awt.event.MouseEvent evt)
    点击表选中一行数据触发事件
    f) private void formInternalFrameClosed(javax.swing.event.InternalFrameEvent evt)
    关闭该内部窗口
    g) private void jButton3ActionPerformed(java.awt.event.ActionEvent evt)
    点击保存按钮触发事件保存成绩
    h) private void showOnTable(List<SelectCourseView> list)
    将数据导入表中

    展开全文
  • 购物车功能模块设计

    万次阅读 2014-10-23 15:47:33
    一:购物车模块功能需求   客户在浏览网页的时候, 当遇到喜欢的商品、 又不急于结账而是继续浏览货 物时。 需要一个购物篮来存储她已经选中的商品。 以便于结账或用于对比商品的 详细参数...
  • 学生选课系统描述:  学生选课系统是一个面向...本文将着重介绍管理员功能模块设计。   语言和环境  A、实现语言:Java  B、实现技术: JavaSE、JDBC、Swing  C、环境要求: NetBeans 8.0.2, SQLServer2008
  • 那么,在进行模块划分的时候,“投递简历”这个功能应该算在“查看职位详情”中,还是算在“投递管理模块”中? ![职位展示](https://img-ask.csdn.net/upload/201906/05/1559673072_455566.png) ![投递管理]...
  • 餐饮大数据-系统架构模块设计

    千次阅读 2017-09-11 10:30:05
    整个餐饮大数据项目经过了团队几个人的齐心协力谈论,初步确定了项目的大体框架,接下来就是设计到项目的... 本平台主要分为两大模块分别面向店铺管理者和店铺主,分别提供不同功能,店铺管理者主要可以查看本商圈的
  • 分布式系统统一登录模块设计

    千次阅读 2018-12-10 10:31:35
    维基百科对于单点登录的定义:  单点登录(英语:Single sign-on,缩写为 SSO),又译为单一...这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一退出(s...
  • 二、功能需求与模块设计

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

    千次阅读 2018-12-06 08:33:01
    B:用例属性:ST 系统测试 IT集成测试 UT单元测试 C:客户管理 D:编号 CRM-ST-客户管理-新增客户-001 2.测试项:一个可以细分的测试的点 3.测试标题:从不同角度对测试用例进行细分,标题不可以重复,对具体测试的...
  • 课程 软件体系结构 实验名称 “电商系统功能模块”需求分析与设计实验课小结 第 页 专业_软件工程___ 班级 2班__ 学号_ 130242014058__ 姓名 吴伟铭 实验日期: 2017 年10 月 07 日 报告退发 (订正 、 重做) 一...
  • 使用Air724模块设计功能DTU

    千次阅读 2020-11-10 09:43:06
    使用Air724模块设计功能DTU 目前物联网开发中,4G模块的使用方式有几下几种: MCU连接4G模块,使用串口通讯,MCU通过AT指令控制数据收发; 4G模块通过USB连接到ARM等系统,通过RNDS或者PPP拨号上网。 4G模块支持...
  • 拍卖系统模块设计方案

    千次阅读 2005-04-30 22:33:00
    拍卖系统模块设计方案——张安春一、用户使用模块: 用户可以参与买卖,信用评价,参加虚拟社区。1、 买家: 参加竞拍,管理购买商品记录,2、 卖家: 普通:发布商品, 专业:店铺管理 页面模板,消息发布,...
  • 比较全的OA系统功能模块列表

    万次阅读 2017-01-04 08:47:48
    国内的协同OA软件总体上比以前更智能了,这是近年来比较重要的...所以今天我收集到一些OA模板,展示出来让大家系统的了解一下OA模板。当然我也会挂一漏万,希望知道更多的人可以补充添加。   企业级项目实战
  • 背景对于很多后台管理系统来说,权限较多,对系统操作的人也会多。如此以来,对于一些操作的记录就非常有必要了,从而可以清楚的追踪对系统进行操作的人以及做了哪些操作,并且可以...下面简单说一下自己设计的思路。
  • 一卡通管理系统 模块详细设计

    千次阅读 2018-12-05 10:02:27
    新建一卡通信息模块设计 1 功能说明 完成对学生申请创建一卡通的请求,注册,系统分配卡ID。 2 流程逻辑 学生向管理员提交个人的信息,系统自动检验信息是否完整,并做出相应的提示信息,系统根据用户的录入,自动为...
  • 为了方便中小型企业想拥有自己的商城而设计,他不单单是一个网上商城,还是一个强大的线下商品进销存管理系统。 二、功能模块 1、用户功能模块 为了更好的对系统进行管理和维护,设定了不同权限的用户来管理商城...
  • 模块管理常规功能自定义系统设计与实现(03--基本系统)
  • 系统架构设计模块拆分维度和原则

    千次阅读 2017-07-07 19:24:14
    在我们从零开始做一个新系统的时候,会首先进行系统功能模块架构设计,那么是直接做一个大而全的垂直的MVC系统,使用一个war包进行发布管理,还是需要按一些规则进行模块拆分,设计成SOA或者微服务系统比较好呢?...
  • 图书管理系统——登录模块设计

    千次阅读 2014-08-05 19:17:11
    1.1登陆模块技术技术分析
  • 模块管理常规功能自定义系统设计与实现 一、设计思想的来源  在我的20多年的编程生涯中所编制的管理型软件里,主要功能都是数据列表展示、录入修改删除、处理、输出、查询、图表这几大块。从最初的foxbase,...
  • 软件设计-模块设计

    千次阅读 2019-04-25 18:22:58
    模块设计1.1 耦合:模块之间的联系紧密程度1.2 内聚:模块内部各元素联系的紧密程度1.3 其他1.4 模块设计注意事项 1. 模块设计 模块设计基本原则:信息隐蔽,模块独立 1.1 耦合:模块之间的联系紧密程度 1. 非直接...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 627,708
精华内容 251,083
关键字:

系统功能模块设计