OO学习体会与阶段总结(设计与实现)

news/2024/7/7 12:54:07

前言

  在最近的一个月的课程中,笔者对于规格化编程进行了深入的学习。运用面向对象抽象思想对编写的程序进行过程抽象、异常处理、数据抽象、类的层次规格与迭代等等规格设计,使得程序结构化程度提高,具有更好的可维护性和复用性。本文通过分析并总结近三次作业规格设计情况,分享我在规格化程序设计上的见解与体会。


作业规格错误汇总

  • 规格错误详细信息:
编号类型所在类方法名称代码行数详细
1前置条件不规范InputHandlerparseOrderReq5未使用形式语言
2前置条件不规范InputHandlerparseRoadChangeReq5未使用形式语言
3前置条件不规范InputHandlerparseSearchTaxiReq4未使用形式语言
4前置条件不规范InputHandlerparseSearchStateReq7未使用形式语言
5后置条件为实现算法LoadFileUnitsetFlow23未使用形式语言表示调用者看到的变化
6后置条件为实现算法LoadFileUnitsetTaxi34未使用形式语言表示调用者看到的变化
7后置条件为实现算法LoadFileUnitsetReq31未使用形式语言表示调用者看到的变化
8后置条件为实现算法MapsetTaxi1未使用形式语言表示调用者看到的变化
9后置条件为实现算法ReqHandlermarkServableTaxi4未使用形式语言表示调用者看到的变化
10后置条件为实现算法ReqHandlerassignTaxi18未使用形式语言表示调用者看到的变化
11后置条件为实现算法ReqHandlerrun12未使用形式语言表示调用者看到的变化
12后置条件逻辑错误TaxiActionrun85,91,931后置条件未描述方法所有的影响
  • 数据汇总:
类型总计平均代码行数最大代码行数
前置条件不规范457
后置条件为实现算法618.534
后置条件逻辑错误189.793
总计1133.693

规格错误分析

第九次作业

  在第九次作业中,根据需求需要加入道路开关功能以及增加用于初始化系统的文件读取指令,并且为所有方法补充过程规格。在这次作业中由于绝大部分代码都是来自上一次作业,许多方法在实现前仅考虑了SOLID原则2而未考虑规格设计,每个方法的规格都是在实现后补充上去的(这在顺序上是倒置的)。有一小部分的方法在功能与产生的作用比较繁杂,难以用形式语言进行描述,最后只能用自然语言作为替换。但对于这些方法使用自然语言也不太好说清楚其后置条件,最后导致了后置条件为算法是实现的过程的错误。此外,在使用形式语言描述的时候,对于一些方法的处理边界的描述上存在一些缺陷。
  在完成这次作业前,笔者学习了使用异常抛出来区分正常情况与异常情况。但由于在此之前设计代码时未考虑使用反射机制来处理异常情况,而使用形如null等变量来当作异常情况的返回。如果要加入这一功能需要重构大部分方法中的讨论情况。由于时间关系,在这次作业中只在新加入的部分使用了异常处理机制。因而在旧代码的部分的规格设计中对于异常情况没有显示表示,而是返回一些无意义的数据(从调用者的角度来看是不友好的)。
  在测试别人的程序时发现的规格问题基本与我的相似,基本是方法的冗杂致使规格的后置条件为实现过程或者后置条件有遗漏。

第十次作业

  在这次作业中,根据需求仅需增加路口的红绿灯功能,工作量较少(虽然计算时间和流量比较困难),因此笔者将之前写的代码根据过程抽象原则进行了优化。对功能较多的方法进行了重构,将其功能进行了分割,分散至不同类或者方法当中。在此之后程序中绝大多数的方法都能够用较为简洁的形式语言描述。因此在这次作业的测试阶段,对方未报告规格错误。
  此外,需求要求补充每个类的类规格、抽象函数以及对象有效性验证方法3。由于最初设计时着重考虑了SOLID原则,程序中每个类的功能是比较明确的。因而增加类规格的困难不大,在互测阶段也没被报告错误。

第十一次作业

  在这次作业中,根据需求需要对出租车种类进行扩充,增加一种能满足新需求(不赘述)的出租车,以及实现迭代输出服务记录的功能。在继承前一种出租车的同时要着重考虑里氏替换原则,实现有效的子类设计,并且还需附有有效性论证。具体体现在子类方法与父类方法的前置条件与后置条件的空间上。在我的程序中,子类重写父类方法过程时仅对后置条件进行了扩充,使其能满足里氏替换原则。因而在互测阶段未被报告错误。在我测试的程序中,设计者将所有父类的方法复制到类子类中,并对细节进行了改动。虽然这种做法有很多赘余,但通过论证也未发现问题。


规格优化

  • 后置条件为实现过程。

 优化前:

public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state)
/**
* @REQUIRES:  0<=index<100;0<=locaX<=79;0<=locaY<=79;
* @MODIFIES:  gui
* @EFFECTS:   (在GUI中将编号为index的出租车设置为state状态,移至(locaX,locaY)位置);
* @THREAD_REQUIRES:\locked(this);
*/

 优化后:

public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state)
/**
* @REQUIRES:   (0<=index<100);(0<=locaX<=79);(0<=locaY<=79);
* @MODIFIES:   gui
* @EFFECTS:    (gui.taxi[index].locaX==locaX)&&(gui.taxi[index].locaY==locaY)&&(gui.taxi[index].state==state);
* @THREAD_REQUIRES:    \locked(this);
*/
  • 前置条件可以扩展,后置条件可以对异常进行处理。

 优化前:

public RoadChangeReq parseRoadChangeReq(String input, long time){
    /**
     * @REQUIRES:   input符合道路更改请求格式;
     * @EFFECTS:    \result==解析后的道路更改请求对象;
     */
    Matcher roadReqMatcher = this.roadReqPattern.matcher(input);
    if(roadReqMatcher.matches()){
        return new RoadChangeReq(roadReqMatcher.group(1),
                roadReqMatcher.group(2),
                roadReqMatcher.group(3),
                roadReqMatcher.group(4),
                roadReqMatcher.group(5),
                time);
    }
    else
        return null;
}

 优化后:

public RoadChangeReq parseRoadChangeReq(String input, long time) throws Exception{
    /**
     * @REQUIRES:   input!=null;
     * @EFFECTS:    (!roadReqPattern.match(input))==>(\result==解析后的道路更改请求对象);
     *              (!roadReqPattern.match(input))==>exception_behavior(Exception);
     */
    Matcher roadReqMatcher = this.roadReqPattern.matcher(input);
    if(roadReqMatcher.matches()){
        return new RoadChangeReq(roadReqMatcher.group(1),
                roadReqMatcher.group(2),
                roadReqMatcher.group(3),
                roadReqMatcher.group(4),
                roadReqMatcher.group(5),
                time);
    }
    throw new Exception("不符合道路更改请求格式");// 可以自定义异常。
}
  • 后置条件应为具体现象。

 优化前:

public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){
    /**
     * @REQUIRES:   0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79;
     * @MODIFIES:   this.set,gui
     * @EFFECTS:    更新出租车位置。
     */
    this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY);
}

 优化后:

public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){
    /**
     * @REQUIRES:   0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79;
     * @MODIFIES:   this.set,gui
     * @EFFECTS:    this.set[taxiNo].locaX==locaX;
     *              this.set[taxiNo].locaY==locaY;
     *              this.set[taxiNo].credit==credit;
     *              this.set[taxiNo].state==state;
     *              gui.taxi[taxiNo].locaX==locaX;
     *              gui.taxi[taxiNo].locaY==locaY;
     */
    this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY);
}
  • 后置条件应为具体现象。

 优化前:

class InputListener implements Runnable {

    private ReqBuffer reqBuffer;
    private TaxiInfoSet taxis;
    private Map map;
    
    ......
    
    @Override
    public void run() {
        /**
         * @MODIFIES:   this.map,this.reqBuffer,this.taxis,System.out
         * @EFFECTS:    (监控到乘客请求)==>(解析并将请求对象加入reqBuffer);
         *              (监控到道路更改请求)==>(解析并更改map中的道路);
         *              (监控到出租车搜索请求)==>(解析并System.out相应信息);
         *              (监控到出租车状态搜索请求)==>(解析并System.out相应信息);
         */

 优化后:

class InputListener implements Runnable {

    private ReqBuffer reqBuffer;
    private TaxiInfoSet taxis;
    private Map map;
    /**
    (省略正则表达式)
    public static String REQREGEX;
    public static String ROADREQREGEX;
    public static String SEARCHTAXIREGEX;
    public static String SEARCHSTATEREGEX;
    */
    ......
    
    @Override
    public void run() {
        /**
         * @MODIFIES:   this.map,this.reqBuffer,System.out
         * @EFFECTS:    (System.in.match(REQREGEX))
         *          ==>(this.reqBuffer.contains(new Request(System.in)));
         *              (System.in.match(ROADREQREGEX))
         *          ==>(this.map.road.status==System.in.status);
         *              (System.in.match(SEARCHTAXIREGEX))
         *          ==>(System.out==this.taxis[System.in.taxiNo].info);
         *              (System.in.match(SEARCHSTATEREGEX))
         *          ==>(\all Taxi taxi;
         *              this.taxis.contains(taxi)
         *              &&taxi.state==System.in.taxiState;
         *              System.out.contains(taxi.info));
         */
  • 后置条件可以写为形式语言。

 优化前:

private void markServableTaxi(ReqWin reqWin)                    
    /**
     * @ REQUIRES:  reqWin!=null;
     * @ MODIFIES:  rewWin;
     * @ EFFECTS:   将符合抢单条件的出租车加入至reqWin的taxiSet中;
     */

 优化后:

private void markServableTaxi(ReqWin reqWin)                        
    /**
     * @ REQUIRES:  reqWin!=null;
     * @ MODIFIES:  rewWin;
     * @ EFFECTS:   (\all TaxiInfo taxi;
     *              this.taxiSet.contains(taxi)
     *              &&taxi.isin(reqWin.district);
     *              reqWin.taxiSet.contains(taxi));
     */

作业功能错误汇总

第九次作业

  在这次作业中笔者被报告了以下三个错误:

  • 错误现象:当读取的文件中有多条乘客请求时程序会死锁。
  • 错误分析:在程序的设计中,初始化乘客请求是通过系统启动前就将文件中的请求解析并加入至请求缓存区中;在此之后调度器将缓存区中的请求取出再按调度策略分配服务的出租车。在最初的程序中,这部分的代码如下:
public class SysMain {
    public static void main(String[] argv) {
        ......
        LoadFileUnit loadFileUnit = new LoadFileUnit(); // 构造文件读取器
        loadFileUnit.checkLoad();                       // 检查指令合法性
        ......
        ReqBuffer reqBuffer = new ReqBuffer();          // 构造请求缓存区
        ......
        ......
        // 构造调度器
        ReqHandler reqHandler = new ReqHandler(reqBuffer, taxiSet, map);
        loadFileUnit.setReq(reqBuffer, map);            // 逐个加入请求
        new Thread(reqHandler).start();                 // 启动调度线程
        ......
}

在此之中请求缓存区的容量为1。进而很明显当请求数大于1时由于此时调度线程还未启动,没有线程能够消耗缓存区的请求,导致了主线程一直等待缓存区为空。而这种情况不会发生,最后导致了死锁。

  • 错误改正:将调度线程的启动时机提前即可。

----------分割线----------

  • 错误现象:多辆出租车同时计算路径时有一定几率报出地图不连通,程序退出并结束程序。
  • 错误分析:在出租车计算路径是需要使用公用的矩阵存储广度遍历的结果,程序中共享资源的互斥存在缺陷导致计算进入了错误的步骤,得出了地图不连通的结果。笔者在原程序中通过对课程组提供的路径计算方法增加synchronized关键词加锁实现资源的互斥,但由于课程组提供的GUI包中大多数类的封装问题,导致该互斥操作不完善,最终导致该错误发生(虽然我测了好久也没出现这种情况)。
  • 错误改正:构造Map类(线程安全类)包装地图操作。

----------分割线----------

  • 错误现象:未排除相同请求。
  • 错误分析:相关代码如下:
// 请求窗口集合类的插入方法
public void append(Object req) {
    Request newReq = (Request)req;
    System.out.println(newReq);
    for(int i= 0; i < this.length; i++) {
        int[] loca = this.list[i].getReq().getLoca();
        int[] aim = this.list[i].getReq().getAim();
        long time = this.list[i].getReq().getMakeTime();
        if(newReq.getLoca()[0] == loca[0]
                && newReq.getLoca()[1] == loca[1]
                && newReq.getAim()[0] == aim[0]
                && newReq.getAim()[1] == aim[1]
                && newReq.getMakeTime() == time) {
            System.out.println("相同请求 : " + newReq);
            // 标记 //
        }
    }
    // 在末尾插入。
    this.list = Arrays.copyOf(this.list, ++this.length);
    this.list[this.length - 1] = new ReqWin(newReq);
    
}

  在对集合中已有的请求进行遍历并判断为相同请求后为中止方法,使相同请求也能被插入至请求队列。

  • 错误更改:在标记处增加return;

----------分割线----------

  在第十与十一次作业时笔者未被报告程序错误。

  在测试别人程序的过程中,笔者测试的这三位同学的程序都无法正常地运行多条乘客请求。在第九次作业时,被测试的程序在运行多条请求时会出现请求间信息错位的现象,初步认定是请求共享部分的互斥工作存在缺陷。在第十次作业时,被测试的程序在运行3至6条程序时会异常地停止运行,无任何反应,但不会崩溃退出。在阅读代码后大致由于路径计算时耗时过多,以致多辆出租车同时计算长距离请求时延迟较大。当运行超过7条请求时,程序会有崩溃的可能,几率随请求数的增加而增加,崩溃的原因是堆栈溢出。在第十一次作业的时候,被测试的程序在运行多条请求时会出现严重的延迟现象。由于这位同学的代码的可读性实在太差,笔者没能找出导致错误的代码。


思考与体会

  在经过这三次作业之后,我对于规格化程序设计的重要性有了亲身体会。在编写面向对象程序前就应当对程序中的数据和处理过程进行抽象,定义出对数据的操作以及数据管理的方式,归纳出程序中需要进行的行为,限定各个操作的边界以及用户可见的内容。再结合上个阶段学的面向对象程序设计原则,对于程序中的类设计做出相应限定,使得编写的类具有更好的延展性、可维护性与鲁棒性。

  经过总结,对于程序中方法的设计过程大致分为以下几步:
1. 明确方法存在的意义。
2. 明确方法结果正确的判定条件。
3. 明确方法对调用者提出的条件,以保证结果正确。
4. 明确方法执行期间修改的数据。
5. 按照要求的方式整理前置条件、修改数据、后置条件。

  经过了短暂的一个月的实践,笔者虽对这些思想有了不少的体会,但还有待更多的实践深化。


  1. 分别为三次作业的代码行数。↩

  2. Solid原则分别指:单一职责原则、 开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。↩

  3. 用于验证有该类生成的对象是否满足该类的不变式。↩

转载于:https://www.cnblogs.com/Cookize/p/9095427.html


http://www.niftyadmin.cn/n/1574779.html

相关文章

我的系统建模工具-EasyStreet 1.0

技术平台&#xff1a;.net 作品介绍&#xff1a;实用的建模工具&#xff0c;能够生成数据层项目代码&#xff0c;生成数据库、数据库脚本&#xff0c;结合内置完善的ORM引擎&#xff0c;能够快速建立管理系统的业务逻辑。输出的数据层可维护性高&#xff0c;扩展容易&#xf…

课件制作工具——wizBuilder CS

技术平台&#xff1a;flex&#xff0c;.net 作品介绍&#xff1a;一个多媒体课程制作工具&#xff0c;通过内置的页面模板、方案模板帮助快速设计课程&#xff0c;让用户只需专注课程知识的填写就能制作出图像&#xff0c;文字&#xff0c;音频等媒体同步的高质量课程。发布的…

touch.js 手机端的操作手势

使用原生的touchstart总是单击、长按有冒泡冲突事件&#xff0c;发现百度在几年开源的touch.js库&#xff0c;放在现在来解决手机端的操作手势&#xff0c;仍然很好用。

企业知识库

技术平台&#xff1a;flex&#xff0c;java 作品介绍&#xff1a;一个RIA的Web应用&#xff0c;对企业知识文档进行管理以及对企业知识文档进行安全控制的平台&#xff0c;主要功能&#xff1a;文档浏览&#xff0c;文档审核&#xff0c;文档管理&#xff0c;文档下载&#x…

RIA技术简单分析

Flex/SilverlightAJAX呈现方式首次访问时加载系统模块&#xff0c;之后只与后台发生最小的纯数据的交互首次访问时加载系统模块&#xff0c;之后只与后台发生最小的纯数据的交互(把AJAX当调味品的只是在开发效率上找一个平衡点)显示效果效果好相对的&#xff0c;效果差开发效率…

从江苏到广东到湖北,ET工业大脑正在改变着中国工业制造业

全球范围内新一轮科技革命和产业变革正在兴起。由于新技术不断地推动着将推动制造业向数字化方向发展&#xff0c;中国也提出了“中国制造2025”,明确推动制造业向数字化、智能化方向发展。云计算、人工智能成为了工业制造业的“新宠儿”。但在智能化的发展中&#xff0c;许多企…

Flex通信篇——Flex和外部进行异步通信

ExternalInterface.call的问题 请留意以下情景&#xff0c;要利用外部应用程序弹出文件选择窗体&#xff0c;并返回所选的文件路径。 使用ExternalInterface.call来实现&#xff0c;当用户60秒(Flex已经设置最长的等待时间)内未能够完成文件选择会弹出"1502"的错误…

分布式缓存Redis应用场景解析

Redis的应用场景非常广泛。虽然Redis是一个key-value的内存数据库&#xff0c;但在实际场景中&#xff0c;Redis经常被作为缓存来使用&#xff0c;如面对数据高并发的读写、海量数据的读写等。 举个例子&#xff0c;A网站首页一天有100万人访问&#xff0c;其中有一个“积分商城…