首页 > 测试驱动, 设计模式, 软件设计 > 面向对象设计、设计模式、代码维护、重构、单元测试关系的思考笔记

面向对象设计、设计模式、代码维护、重构、单元测试关系的思考笔记

2009年9月12日 admin 发表评论 阅读评论

这篇文章是我个人对面向对象设计、设计模式、代码维护、重构、单元测试之间关系的一些思考笔记,内容写的比较粗糙,在以后会不断地完善文章的内容、和示例。

例如,我们有一个场景需要实现在某个人给Blog发了一个Comment以后,就发一份邮件给Blog的host,在邮件中说明comments的内容,外加一个批准这个Comment的url link。

一个典型的实现可能如下面这段代码

public class CommentAction {
    private CommentService commentService;

    private MailSender mailSender;

  /**

   发布评论方法。

  */

    public void doComment(Rundata rundata) {

        Comment comment = getComment(rundata);
        Boolean resesult = commentService.doComment(comment);
        if (resesult) {
            Mail commentMail = genContent4CommentNotificationMail(comment);
            mailSender.sendMail(commentMail);
        }
    }

  /**

   获取评论数据

*/

    private Comment getComment(Rundata rundata) {
        Comment comment=new Comment();
        comment.setCotent((String)rundata.get("content"));
        comment.setPoster((String)rundata.get("poster"));
        comment.setTimeSubmited((Date)rundata.get("timeSubmited"));
        return comment;
    }

/**

   产生发送邮件的内容。

*/

    private Mail genContent4CommentNotificationMail(Comment comment) {
        Mail mail = new Mail();
        mail.setBody(comment.getContent());
        mail.setTitle("您的Blog有一封新的评论");
        return mail;
    }

}

我们在描述CommentAction 的功能描述应该是“向系统发表一篇Comment然后,生成提醒邮件发送给Blog的Host”。

从业务发生变化的角度来分析,以下几种业务的变更都会导致CommentAction 的频繁修改

  • 评论内容和格式的变化
  • 增加新的提醒方式
  • Email提醒的内容、格式发生变化
  • 等等

从面向对象的设计原则来分析这块代码,从“向系统发表一篇Comment然后,生成提醒邮件发送给Blog的Host”这个描述中开发人员应该体会到CommentAction 包含了至少2项功能,意味着这个类目前的实现已经破坏了单一责任原则。CommentAction 就是一个God类,包含了实现这个UC几乎全部的逻辑。

从代码维护的角度来分析这块代码,随着业务逻辑越来越复杂,相应的实现也会变得越来越复杂,代码越来越不清晰,代码的维护越来越困难。

从单元测试的角度来分析这块代码,对CommentAction 的doComment方法的测试,需要Mock commentService和MailSender 这两个类。单元测试的开发、维护都会困难。CommentAction 其中某个逻辑的修改都有可能破坏CommentAction 其他的单元测试。

所以解决的方法是对CommentAction 进行重构。我做的重构如下,仅供参考。

public class CommentAction {
    private CommentService commentService;
    private List<CommentEventListener> commentEventListeners;

    public void doComment(Rundata rundata) {
        Comment comment = getComment(rundata);
        commentService.doComment(comment);
        fireEvents(comment);
    }

    private void fireEvents(Comment comment) {
        CommentEvent commentEvent=new CommentEvent(comment);
        for (CommentEventListener commentEventListener : commentEventListeners) {
            commentEventListener.fireEvent(commentEvent);
        }
    }

    private Comment getComment(Rundata rundata) {
        Comment comment=new Comment();
        comment.setCotent((String)rundata.get("content"));
        comment.setPoster((String)rundata.get("poster"));
        comment.setTimeSubmited((Date)rundata.get("timeSubmited"));
        return comment;
    }   
}

public class CommentEvent   {

    private Comment comment;
    public CommentEvent(Comment comment) {
        this.comment=comment;
    }
    public Comment getComment() {
        return comment;
    }

}

public interface CommentEventListener {

    void fireEvent(CommentEvent commentEvent);
}

public class CommentEventMailNotifactionListener implements CommentEventListener {
    private MailSender mailSender;

    private Mail genContent4CommentNotificationMail(Comment comment) {
        Mail mail = new Mail();
        mail.setBody(comment.getContent());
        mail.setTitle("您的Blog有一封新的评论");
        return mail;
    }
    @Override
    public void fireEvent(CommentEvent commentEvent) {
        mailSender.sendMail(genContent4CommentNotificationMail(commentEvent.getComment()));
    }
}

重构以后的CommentAction中主要进行的就是comment的发布,另外就是触发了commentEvent事件。邮件的发送逻辑被移植到了CommentEventMailNotifactionListener 来实现,解除了CommentAction和MailSender 类的耦合。CommentAction 不再是个God类,实现这个UC的逻辑由几个类协作完成。

从业务发生变化的角度来分析重构以后的代码

  • 评论内容和格式的变化——只需要修改CommentAction
  • 增加新的提醒方式——只需要扩展新的CommentEventListener
  • Email提醒的内容、格式发生变化——只需要修改CommentEventMailNotifactionListener

从代码维护的角度来分析重构以后的代码,CommentEventMailNotifactionListener 、CommentAction 都只关注他们各自的逻辑,代码清晰、实现简单、维护也很容易,修改了之后对其他模块的破坏几率也很小。

从单元测试的角度来分析这块代码,对CommentAction 的doComment方法的测试,只需要Mock commentService这个类就可以了。对邮件的提醒的单元测试只需要对CommentEventMailNotifactionListener 进行单元测试就可以了。重构前后单元测试的开发、维护的难度和成本都会得到控制。

从上面这个例子中,我们可以总结出以下几点:

  • 编写易读、易维护、可扩展性好的代码是我们的最大目标。
  • 要实现这个目标需要遵循很多面向对象的设计原则。
  • 设计模式的运用可以帮助我们达到这个目标,后者说使用合理的设计模式就可以实现这个目标。
  • 实现这个目标需要通过代码的不断重构来达到。
  • 保证重构以后的代码还是可以继续工作的,就需要单元测试来保护我们的每一次重构。
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.