OO-Unit4-总结

杨荣津-21375077 学生 2024-06-14 18:04:07

概览

1.总结本单元所实践的正向建模与开发

  • 正向建模
    在Java编程中,正向建模(Forward Modeling)通常指的是从设计阶段开始,将系统的需求和规范逐步转化为详细的实现代码。这种方法包括从概念和需求分析出发,通过逐步细化设计,最终编写出完整的代码实现。
    需要进行

    1. 需求分析
    2. 概念建模
    3. 详细设计
    4. 编码实现
    5. 测试与验证
    6. 文档编写
  • 本单元的正向建模

    • 在本单元的三次作业中,逐步迭代完成一个具有查询书籍、借阅、预约、还书、预约取书、续借、捐书和查询用户操作的图书馆管理系统
      private void queryBookFormal(){}
      private void queryBookDrift(){}
      private void borrowBookFormal(){}
      private void borrowBookDrift(){}
      private void orderBook(){}
      private void returnBook(){}
      private void pickBook(){}
      private void renewBook(){}
      private void donateBook(){}
      public static void queryUser(LibraryQcsCmd libraryQcsCmd){}
      
    • 在第一次作业中,需要完成查询书籍、借阅、预约、还书、预约取书的操作,涉及到书架、预约处、借还处。针对第一次的作业,我首先使用UML进行了初步的模型设计,主要有以下考虑
      1. 所有的操作通过Service实例完成,每个Service实例对应一个Req
      2. 图书馆,预约处,借还处采用单例模式
      3. 因为书籍需要频繁的移动,因次对于图书馆,预约处,借还处和用户都继承一个Place接口,有利于统一管理书籍
  • 本单元的开发

    • 然后基于初步的设计,再进行开发
    • 在开发的过程中,新增了Deliver类。这个类和Service类不同。
      • Service类用于响应图书馆的各种操作,控制图书馆,借还处,预约处响应用户的需求
      • Deliver类专门用于移动书籍,所有从Place from移动到Place to的操作都由Deliver类完成;图书馆闭馆后的整理操作也由Deliver完成。因此Deliver中的方法都为静态方法
        public class Deliver {
            public static void moveBook(LibraryReqCmd req, Place from, Place to){}
            public static void addReturned(LibraryReqCmd req){}
            public static void updateDate(LocalDate date){}
            public static void organizeBook(){}
        }
        
    • 最终整个架构分为了三个部分:
      • Main
      • utils(Service,Deliver,Check)
      • places(BookShelf,AppoOffice,BoReOffice...)
  • 两者的关系

    • 在开发前进行建模和详细的设计,有助于开发的顺利进行,避免了在开发过程中反复修改,层次不清晰的问题。
    • 开发过程中,会调整初步设计中不合理的地方,进一步完善设计。因此在首次设计中,很难把所有要考虑的问题和细节都兼顾,但是在开发过程中这些问题会暴露出来,从而调整我们设计的模型。

2.总结本单元作业的架构设计,并对比分析最终的代码设计和UML模型设计之间的追踪关系

  • 本单元架构设计

    • Main中实现四大操作的基本响应
      if (command instanceof LibraryOpenCmd) {/* 执行开馆操作 */} 
      else if (command instanceof LibraryCloseCmd) {/* 执行闭馆操作 */} 
      else if (command instanceof LibraryQcsCmd) {/* 执行查询用户操作 */} 
      else {/* 执行图书服务操作 */}
      
    • Service中通过execute()调用各种图书服务操作,以实现图书服务
    • Deliver中完成移动图书、整理图书的需求
    • 采用统一的Place接口,需要实现四个抽象方法:
      public interface Place {
          void addBook(LibraryReqCmd req, int numToAdd);
          void removeBook(LibraryReqCmd req);
          boolean containsBook(LibraryReqCmd req);
          void check();
      }
      
      • 因为对于书架、预约处、借还处、用户都会涉及到addBook,removeBook,containsBook,check的操作
      • 同时这样有利于Deliver.moveBook()方法的简化,对于移动书籍都转换为Place类
        public static void moveBook(LibraryReqCmd req, Place from, Place to){}
        
    • 最终的架构图如下
  • 最终的代码设计和UML模型之间的追踪关系

    • 最终的代码设计和UML模型之间的对应关系良好
    • 并且实现了以下关联关系:
      • 对于BookShelf,DriftCorner采用单例模式,并且构造方法为public
        public class BookShelf implements Place {
            public BookShelf() { }
        }
        public class DriftConer implements Place {
            public DriftConer() {}
        }
        
      • 对于AppoOffice采用单例模式,并且构造方法为privateAppoOffice中维护一组保存在预约处的书籍
        private LinkedList<BookReservation> savedOrders = new LinkedList<>();
        
      • 对于BoReOffice,User,均含有属性为BookShelf实例和DriftCorner实例,用于管理存储在借还处或者用户处的正式书籍,漂流书籍。其中BoReOffice采用单例模式
        public class BoReOffice implements Place {
            public static final BoReOffice boReOffice = new BoReOffice();
            private final BookShelf bookShelf = new BookShelf();
            private final DriftConer driftConer = new DriftConer();
        }
        public class User implements Place {
            private final BookShelf bookShelf = new BookShelf();
            private final DriftConer driftConer = new DriftConer();
        }
        
      • 所用的User集合到一个UserTable中,UserTable采用单例模式,用于对每个用户执行一些统一的操作,比如给所有用户处的书籍增加已借阅日期
        public class UserTable {
            public static final UserTable USERS = new UserTable();
            private UserTable() {}
            public boolean hasUser(String username) {}
            public void addUser(String username) {}
            public void addOneOwnDay() {}
            public void checkOverdue() {}
        }
        
    • 可以看出最终代码的设计相比于起初的UML设计没有发生大的变动,只是增加了一些细节,两者之间追踪关系较好

3.总结自己在四个单元中架构设计思维的演进

  • U1 递归下降

    • 在U1中,我的架构思维还比较初步,对于问题的思考和架构主要参考往年学长的博客

    • 对于递归下降解析一个复杂的表达式,使用一个统一的输入处理器Process,表达式解析器Parse,和Expr,Term,Factor三个类去解析这个表达式

    • 在模仿和思考的过程中,我逐步学会了一些架构设计的思维,尤其是在U1的第二次作业中,如何合并同类项。在第二次作业的形式化表达( Unit = ax^nexp( \sum Unit ) )下,判断两个单项式相等不仅要判断a和n,还要判断exp中的(\sum Unit)是否相等,而每个(Unit)中仍有可能继续嵌套(\sum Unit)。参考学长的做法,以及和同学们思考讨论之后,得出了递归判等的方法。伪代码如下:

      1. 首先对两个Unit的a和n判断是否相等
      2. 然后对Unit中exp()内的Poly判断是否相等
        • 把Poly中的(\sum Unit)映射成一个二级HashMap,第一层Key是x的系数n,第二层Key是exp()内的多项式poly,最后映射到的Value是在这个n和poly下对应的Unit
        • 通过对两个Unit的两层HashMap进行四层的循环遍历判等,如果遇到poly不为空就要递归调用
      3. 设置递归出口
        • 最终递归到Unit的exp()中poly没有含有有效的项,此时就可以只要通过a和x的指数n来判断两个Unit是否是同类项
      • 在完成了( Unit = ax^nexp( \sum Unit ) )的同类项判断之后,在第一次作业的基础上修改用于合并同类项的方法addPoly(),powPoly(),addUnit()即可
      • 类图如下:
  • U2 多线程

    • 在U2中,我开始尝试自己设计架构
    • 在第一次的作业中,由于调度要求比较简单,因此设计的架构也比较简单
      直接由Distributor线程完成输入的读取和分发,由多个Elevator线程完成运送乘客
    • 在第二次的作业中,调度变得复杂,因此需要完善架构的设计,增加了Scheduler类。因为在第二次作业中,不再指定需要乘坐的电梯,而是自定策略来把请求分配给电梯,以达到更好的性能;同时由于调度类的增加,也相应的需要增加一个ProcessQueue类,并且设置单例模式,作为请求未分配前的总请求队列。
    • 第二次到第三次的作业中,增加了Controller类,因为前两次的作业中,电梯的运行逻辑直接在电梯中实现,包括了重置,开关门,进出乘客,运行。
    • 顺序图如下:
  • U3 JML

    • 在U3中,涉及架构设计较少,因为主要根据课程组的要求来实现这些类
    • 但是可以从提供好的框架中学习到一些架构设计的思想
      • 使用一个MyNetwork来实现对社交网络中各种请求的响应
      • 进一步的细化操作,比如增减熟人,发送消息则通过MyPerson,MyMessage这些类来具体实现
  • U4 UML

    • 在U4中,我主要靠自己完成了架构的设计
    • 架构设计前文已叙述
  • 设计思维的演进

    • 在四次作业中,通过模仿、学习学长的代码,并加以自己的思考,我架构设计的思维得到了一定的提升,从一开始的依赖他人架构,逐步走向自己思考如何设计出合理的架构,我总结出架构设计的以下三点经验:

      • 根据需求划分
        在拿到设计要求的时候,我们首先要认真阅读指导书,对于每次需要实现的需求和功能进行初步的分类。并且一个需求可能会涉及到多个步骤,每个步骤如何完成,交给哪个类完成,是否需要为某个步骤单独考虑而建立一个类都是需要思考的。

      • 进一步细化职责,降低耦合度
        思考好之后,可能我们会开始写一些初步的代码,在这个过程我们会发现更多起初没有考虑到的细节,从而可以考虑得更加全面,然后调整每个类在处理请求中的地位,应该承担的功能,把相似的请求或者有密切关联的功能实现在同一个类中,降低类与类之间的耦合度

      • 增加工具层,统一化响应
        并且,在四次作业中,我发现了建立工具类来完成对各种请求的统一响应是使得代码层次清晰,架构合理的一个常用方法

        • 比如在U2中大多数同学都采用了Controller类来统一指挥电梯下一步的运行应该是OPNE_AND_CLOSE,SPE_MOVE,MOVE,TURN,WAIT,RESET,SUB_RESET,FINISH中的哪一种

          public Instruction getInstruction() {
              if () { return Instruction.RESET; }
              else if () { return Instruction.SUB_RESET; }
              else if () { return Instruction.OPNE_AND_CLOSE; }
              else if () { return Instruction.MOVE; }
              else if () { return Instruction.TURN; }
              else if () { return Instruction.FINISH; }
              else { return Instruction.WAIT; }
          }
          
        • U4中采用Service类来统一完成对QUERIED, BORROWED, ORDERED, RETURNED, PICKED, RENEWED, DONATED操作的响应

          public void execute() {
              switch (req.getType()) {
                  case QUERIED:
                      if (isFormal) { queryBookFormal();
                      } else { queryBookDrift(); }
                      break;
                  case BORROWED:
                      if (isFormal) { borrowBookFormal();
                      } else { borrowBookDrift(); }
                      break;
                  case ORDERED:
                      orderBook();
                      break;
                  case RETURNED:
                      returnBook();
                      break;
                  case PICKED:
                      pickBook();
                      break;
                  case RENEWED:
                      renewBook();
                      break;
                  case DONATED:
                      donateBook();
                      break;
                  default:
                      break;
              }
          }
          

4.总结自己在四个单元中测试思维的演进

  • U1构造嵌套多项式 + 正确性判断

    • U1是我第一次尝试自己搭建测评机

    • 第一次搭建的测评机主要分为两个部分:数据生成gene.py和运行程序main.py

      • gene.py根据递归下降解析表达式的思想,分别设计了gene_expr(), gene_term(), gene_factor(), gene_num(), gen_power()等方法,递归地生成测试数据
        并且为了控制 括号嵌套在一层 专门设计了一套gene_expr(), gene_term(), gene_factor(), gene_num(), gen_power()的副本,用于生成不会含有FactorExpr的表达式,以保持括号不会多层嵌套
      • main.py通过比对sympy对测试数据和java程序对测试数据的化简结果,判断次测试数据是否被正确化简
    • 第二次搭建的测评及沿用第一次测评机的框架

      • gene.py做比较大的逻辑上的调整
        由于第二次作业中会出现多层括号嵌套,因此可以使用同一套生成方法。
        但是嵌套层数过多会导致递归过深,从而导致生成测试数据的程序被强制停止,因此仍然需要设置嵌套深度,创造递归出口。
        我通过一个嵌套深度的参数来解决这个问题。
        在gene_expr()方法中传递一个参数depth,当调用到gene_factor()的时候depth--,并且继续向下传递;当depth==0的时候需要在gene_factor()禁用gene_factor_expr(),gene_factor_fun()等方法,停止继续嵌套
  • U2构造电梯请求

    • U2我没有搭建数据生成器,但是我搭建了输出解析器,帮助更快速的寻找输出中有问题的地方
    • 这个单元的正确性判断不同于U1一样,答案相对比较固定,可以通过python提供的库进行正确定判断。U2电梯调度的输出结果根据每个人的调度策略,以及每个电梯线程在争夺时间片时的先后都会有差别,这时就需要对输出进行解析,从电梯运行的角度去判断是否有逻辑上的问题,比如有没有乘客进去了没有出来,有没有电梯在重置期间发生移动等
  • U3构造社交请求

    • U3我搭建了数据生成器,而对于输出由于是有唯一答案的输出,因此采用了对拍的方法来实现正确定判断

    • 数据构造的思路就是根据各种操作,完成生成每种操作数据的方法,并且通过随机数来触发各种异常分支,达到可以认为控制覆盖率的目的。

      • 以mr指令为例,生成mr指令的格式为mr id1 id2 value, 因此可能触发的错误有:
        1. 网络中不存在id1或者id2这个人
        2. 网络中存在id1和id2,但是id1和id2相等
        3. 网络中存在id1和id2,并且id1和id2不相等,但是两个人之间不是熟人
        4. 网络中存在这两个人并且是熟人,则进行mr的增值操作
        5. 网络中存在这两个人并且是熟人,则进行mr的删除操作
          def mr():
           choose = random.randint(1,5)
           if choose==1:
               mr_wrong_unexisted_id()
           elif choose==2:
               mr_wrong_unexisted_rel()
           elif choose==3:
               mr_wrong_same_id()
           elif choose==4:
               mr_set_value("add")
           elif choose==5:
               mr_set_value("delete")
          
    • 同时设计了config参数来修改社交网络的规模,操作指令的条数,达到对程序进行压力测试的目的

      # 2. operate times
      times={
          "ap_times":150,
          "ar_times":500,
          "mr_times":100,
      
          "at_times":500,
          "dt_times":100,
          "att_times":2000,
          "dft_times":200,
      
          "am_all_times":4000,
          "sm_times":800,
          "cn_times":50,
          "sei_times":100,
      
          "dce_times":50
      }
      
  • U4进行书架检查

    • 在U4中我在程序自身中实现了Check类,在BookShelf,AppoOffice,BoReOffice...等类中实现了check()方法,来查看图书馆的情况
      CLOSE: 2024-06-12
      --------------------
      书架:
      A-8175 : 5
      ----------
      B-0074 : 0
      B-3378 : 1
      B-5645 : 3
      B-6328 : 3
      B-6894 : 5
      B-8903 : 1
      ----------
      C-4217 : 0
      C-6627 : 0
      C-6685 : 0
      --------------------
      借还处:
      B-0074 : 0
      B-3378 : 0
      B-5645 : 0
      /* ... */
      /* ... */
      /* ... */
      --------------------
      用户:
      60997402: 
      B-8903 : 1
      ----------
      C-6685 : 1
      CU-5618 : 1
      {CU-5618=0}
      信用积分:14
      {CU-5618=14, B-8903=30, C-6685=60}
      {CU-5618=64, B-8903=1, C-6685=143}
      /* ... */
      /* ... */
      /* ... */
      
  • 测试思维的演进

    • 在四个单元中,为了测试程序,我学会了构造数据,正确性判断,对拍,打印程序内部信息等方法来检查程序的正确性
    • 从第一单元对测评机的没有概念,到后来从根据每次作业特点,有针对性的从某个测试的环节着手来完成程序的测评,我的测试思维得到了逐步的训练

5.总结自己的课程收获

  • 学会了搭建测评机

    • 在学习这门课之前,我并没有想过如何制造测试数据,只是觉得很神秘
    • 接触到了互测之后,才发现原来构造测试数据只是测试程序的一部分。
      • 测试我们所完成的代码,需要思考如何构造出能触发更多分支情况的数据
      • 需要思考如何对输出的结果进行正确性判断,尤其对于输出结果不唯一的程序,如何从逻辑层面去解析输出,判断输出是否正确,并定位出现的bug和类型
    • 我认为这是一项十分有用的技能,帮助我们建立起了程序测试的思想。让我深刻理解了测试的重要性,它不仅是发现问题的过程,更是提升程序质量的关键步骤,为未来的编程和测试工作奠定了坚实的基础。
  • 学会了架构设计

    • 在四个单元的不断迭代中,我逐步学会了架构设计,这是十分有意义的
    • 帮助我们提升我们所设计的系统稳定性和性能
    • 帮助我们提高开发效率
    • 帮助我们增强系统可维护性,促进系统的可扩展性
    • 有利于团队协作
      比如在UML和JML两个单元中,建立起统一的架构和JML规范,便可细致化地给不同的程序员分派任务,同步进行一个大型项目的开发,有利于团队协作
...全文
64 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧