目录
一、正向建模与开发
在开始UML这一单元的学习时,我对先进行代码实现还是先进行UML绘图进行了一个深入的思考。首先,我们要画的UML图只是类图而已,其中并不包含任何类之间的具体关系,有的只是如关联、依赖这样比较高层次的概括性强的关系总结。而在面向具体需求,进行代码的具体实现之中,可能遇到的问题是难以预见的。在遇见并解决问题的过程中,我们可能会不断改变类,改变方法等等。若是先进行UML绘图,再进行代码实现,修改时需要同时修改代码和UML,这样比先进行代码实现要低效很多。因此,我决定先进行代码的具体实现,最后根据代码完善相关的UML类图。
但是,先进行代码实现,这并不意味着我们一上来就是“库库”地敲代码。我选择是先进行代码框架的实现,但是,在这一过程,我也先同步在UML类图中画好了类+类的属性,或者这也许就是课程组的“先UML,后代码”的过程?虽然UML是最先开始的,但是UML它也是最后才完成的,因此我也无法完全领悟课程组的意图。
在进行设计时,我就已经按照图书馆运行的结构确定好了所有基础类图,当然在之后迭代也会添加新类图,但那也是“锦上添花”,于大框架无关紧要了。所有类图如下所示:
其中的status和CrossingOffice都是后续新增漂流角而相应增加的,其余都是第一次作业最开始就确定好的。细心的朋友可能会发现,其中的QueryMachine似乎比较不常见。因为简单的询问书本操作完全可以在图书馆或者书架BookShelf中用一两个函数就实现了。我想解释的是,这就是先进行架构实现时考虑不到的了,我最开始以为这个询问操作或许会更复杂,比如询问具体某本书,某种书,或者说是书和书之间的关系,这样单独开辟一个询问机器类或许可以更好的实现低耦合的要求,但是后续任务并没有这样新增要求,因此,完成这个单元后看来,这个类就有点多余了。
以上就是我的进行正向编程中的开始阶段,比较有意义的内容了,后续的迭代过程我将在作业后续架构变化中给出具体解释和分析。
二、架构设计总结和UML图追踪
1.架构设计总结
总的来说我的架构设计其实是比较明了的,这里我贴出最后一次作业的UML类图,并据此分析:
从上图可以看出我的关键类就是Library类,基于这个类,衍生出来了借还处ServeOffice、预约处AppointOffice、查询机器QueryMachine、漂流角CrossingOffice机构执行具体的功能或者交互动作。而Library类则具体负责解析命令,并分发处理命令。如下:
解析命令:
while (true) {
LibraryCommand command = SCANNER.nextCommand();
if (command == null) { break; }
today = command.getDate();
if (command instanceof LibraryOpenCmd) {
appointOffice.updateDate(today);
serveOffice.updateDate(today);
dailyAppoint = new DailyAppoint(today);
booksFromServeOffice = new HashMap<>();
booksFromBookShelf = new HashMap<>();
todayAcceptedOrders = new ArrayList<>();
// before open check AppointOffice invalid book
HashMap<String, HashSet<LibraryBookId>> appointments;
ArrayList<LibraryMoveInfo> libraryMoveInfos = updateCrossingBooks();
while ((appointments = appointOffice.hasInvalidBooks(today)) != null) {
// check and get when has invalid books in AppointOffice
appointOffice.update();
// ........
}
for (Student student : students.values()) {
student.changeCredit(-(student.numOfNewOveredBook(today) * 2));
}
PRINTER.move(today, libraryMoveInfos);
ListIterator<LibraryReqCmd> li = unRespondOrders.listIterator();
while (li.hasNext()) {
LibraryReqCmd unRespondOrder = li.next();
dealUnRespondOrder(unRespondOrder, li); }
} else if (command instanceof LibraryCloseCmd) {
// after closed : move books from serveOffice to bookshelf
// AND move books to AppointOffice
// AND move booksFromServeOffice to AppointOffice
//.......
} else if (command instanceof LibraryQcsCmd) { // 信用积分查询
LibraryQcsCmd req = (LibraryQcsCmd) command;
if (!students.containsKey(req.getStudentId())) {
Student student = new Student(req.getStudentId());
students.put(req.getStudentId(), student); }
PRINTER.info(command,
students.get(((LibraryQcsCmd) command).getStudentId()).getCredit());
} else {
LibraryReqCmd req = (LibraryReqCmd) command;
if (!students.containsKey(req.getStudentId())) {
Student student = new Student(req.getStudentId());
students.put(req.getStudentId(), student); }
dealRequest(req);
}
}
}
这里其实将开关门也提取成一个函数会好看很多,但是清楚的同学应该了解这样也需要在类图增加函数,为了偷懒不想干活减少负担减少失误的可能,就没有去实现
分发处理命令:
private void dealRequest(LibraryReqCmd request) {
if (request.getType() == Type.QUERIED) {
dealQueried(request);
} else if (request.getType() == Type.BORROWED) {
dealBorrowed(request);
} else if (request.getType() == Type.ORDERED) {
dealOrdered(request);
} else if (request.getType() == Type.RETURNED) {
dealReturned(request);
} else if (request.getType() == Type.PICKED) {
dealPicked(request);
} else if (request.getType() == Type.RENEWED) {
dealRenewed(request);
} else if (request.getType() == Type.DONATED) {
dealDonate(request);
}
}
而具体的部门只是实现了相关的交互功能,就拿ServeOffice来说:
import com.oocourse.library3.LibraryBookId;
import com.oocourse.library3.annotation.Trigger;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
public class ServeOffice {
private LocalDate today;
private HashMap<LibraryBookId, Integer> books;
private HashMap<String, Student> students;
public ServeOffice(HashMap<String, Student> students) {
this.students = students;
books = new HashMap<>();
}
public void updateDate(LocalDate date) {
today = date;
}
@Trigger(from = "formal", to = "stay_serve")
public boolean checkBorrow(LibraryBookId bookId, String studentId) {
//
}
public void decideBorrow(LibraryBookId bookId, String studentId) {
removeBook(bookId); //let student borrow the book
students.get(studentId).borrowBook(bookId, today);
}
public boolean decideReturn(LibraryBookId bookId, String studentId) {
//
}
public void removeBook(LibraryBookId bookId) {
//
}
public void addBook(LibraryBookId bookId) {
//
}
public boolean hasBook(LibraryBookId bookId) {
if (books.containsKey(bookId)) {
//
}
public HashMap<LibraryBookId, Integer> getBooks() {
return books;
}
public void clearBooks() {
this.books.clear();
}
}
其中实现了加书删书等交互行为,其他的就没有了。
2.UML设计时的追踪
下面是我最开始的UML类图的绘画:
(1)图版本一
可以看到和最终的UML类图相比还是差了挺多内容的,但是基本的框架都已经有了。
接下来进行第一次迭代:
(2)图版本二
可以明显看到,新增了CrossingOffice实现漂流角的相关实现,以及增加了ENUM类Status,用来模拟捐赠图书复杂的状态转变,以及转正操作。同时,Library和Student类都有属性的调整和方法的增加。用来实现迭代新增的要求。
(3)图版本三
这里重复给出迭代两次后的UML类图:
这里同样可以明显看出,变化最大的是Student类,新增了很多属性以及方法。这里也可以相应的看出第二次迭代是聚焦于学生信用的变化的。当然,Library类里相关受信用分影响的部分也是需要调整和改变的。
三、架构设计思维的演进
实际上在UML设计时的追踪一节就已经解释了些架构演变的内容了。这里就稍微总结一下。就是在最开始就已经设计好了以Library为功能顶层的类,以后的所有实现基于Library提供的交互,实现相应的接口,或者说是机构类即可。这样实际上,之后的架构设计已经没有太大改变了。而这种架构最初的设计也是一开始就想好了,并没有遗漏什么。非要说改进的话,我觉得可以把QueryMachine类换成Library类里的一个方法就行。
四、测试思维的演进
实际上,关于测试思维我的设想还是比较少的。由于课程组保证了测试时只对基本功能进行测试,而不会对边界情况等等进行测试。因此,这样我们只需要对我们的代码各种功能进行全覆盖模拟就行了,也就是:排列组合。可以借助我们实现的顺序类图,对各种消息传送的先后顺序进行排列组合,这样即可以测试代码,也可以测试顺序图合理性,一举两得!
五、课程收获
这一单元的收获还是很多的。完整地学习了UML的类图、状态图、顺序图以及相关的知识。学会了如何先从顶层开始设计,之后一步一步进行改进和完善就行了。帮助我们学会了更多地在敲代码前的工作该如何进行更充分的思考。
而对于这门课程的收获,我在之前的单元总结里也说了很多了。这里说一下总体感受吧。OO这门课还是很好的完成了它该有的作用的。教会了我们很多关于面向对象编程的知识。但是实际上“面向对象”我们在先导课已经感触够“充分”了。正课中更多的则是补充知识,包括但不限于“递归下降解析字词”、“多进程调度处理同步互斥”、“争议很大深受其害的JML”、“简单舒心让人快乐的UML”(快乐部分不包括画图,画图还是挺容易出错的)。这样就组成了我们的OO课程。
实际上我对于OO的体验是没那么满意的,中测的测试不完善,会把小错误放进强测造成:大分数消失。种种原因导致OO得分并不高。希望我和OO能一起进步吧()