面向对象设计、设计模式、代码维护、重构、单元测试关系的思考笔记
这篇文章是我个人对面向对象设计、设计模式、代码维护、重构、单元测试之间关系的一些思考笔记,内容写的比较粗糙,在以后会不断地完善文章的内容、和示例。
例如,我们有一个场景需要实现在某个人给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 进行单元测试就可以了。重构前后单元测试的开发、维护的难度和成本都会得到控制。
从上面这个例子中,我们可以总结出以下几点:
- 编写易读、易维护、可扩展性好的代码是我们的最大目标。
- 要实现这个目标需要遵循很多面向对象的设计原则。
- 设计模式的运用可以帮助我们达到这个目标,后者说使用合理的设计模式就可以实现这个目标。
- 实现这个目标需要通过代码的不断重构来达到。
- 保证重构以后的代码还是可以继续工作的,就需要单元测试来保护我们的每一次重构。
