結合 uml 所學和 Javafx 從建模到實作一個子功能模塊 —— 日程管理,新手上路,類圖到代碼實作的程序還是很曲折但所幸識訓頗豐,記錄一下學習心得,
日程功能模塊
最后成果
JAVAFX里面沒有封裝日歷控制元件,找了些專案原始碼做參照肝了一個,不過為了簡化分析的程序,不會詳細寫其中業務邏輯,
總的來說,從建模帶代碼實作的功能完成了90%,日程串列和日歷的通信沒寫,預期效果是如果有新增的日程,日歷上相應的日歷塊會有 marked 的標記,這一塊按照我的想法寫代碼會變得很亂,也是建模程序中沒有認真考慮的點,


建模程序
需求
日程主要是幫助用戶查看和管理日常事務,用戶可以記錄待辦事件并且設定提醒時間,有助于管理時間和提高作業效率,
而作為成績管理系統學生界面的一個子功能模塊,除了幫助學生管理課程和學習任務,它還需要能自動匯入課程考試資訊,便于學生規劃學習進度,
用例圖及主要用例描述
- 用例圖
從需求分離出物件為學生,把所有有意義的動賓短語先列出來【查看日常事務】,【管理日常事務】,【記錄待辦事件】,【設定提醒時間】,【自動匯入課程考試資訊】,這里是因為需求比較直觀簡單,一般還需要根據語意找隱藏的功能需求,
進一步分析用例之間的關系,【管理日程事務】即對日程做刪改,應該是在選定具體的事務后,【記錄待辦事件】即新建日程,包括【設定提醒時間】等資訊設定,【自動匯入課程資訊】應該不是由學生完成的,學生只能查看,所以還有參與者 —— 考試管理系統,
畫出用例圖如下
心得:在畫用例圖時并沒有花太多時間打磨,構建的快也修改的快,從需求快速提取用例,在寫用例描述的時候還會再倒回來修改的

參考老師發的資料,從用例圖如何到類圖,觀點不一,有從活動圖 ——> 類圖以及從詳細的用例描述中抽象出類圖,兩種都參考嘗試了一下,
- 用例描述
| 用例編號 | S1.1 |
| 用例名稱 | 查看日程詳細資訊 |
| 參與者 | 學生 |
| 觸發條件 | 點擊串列中具體的一項日程 |
| 前置條件 | 學生已經登錄,并且在日程界面 |
| 后置條件 | 顯示目標日程的詳細資訊 |
| 正常流程 | 1. 點擊串列中目標日程,顯示日程資訊 |
| 擴展流程 | 1. 編輯目標日程,修改事件資訊 2. 洗掉目標日程 |
| 特殊要求 | 無 |
| 用例編號 | S1.2 |
| 用例名稱 | 新增日程 |
| 參與者 | 學生 |
| 觸發條件 | 點擊“新增日程”按鈕 |
| 前置條件 | 學生已經登錄,并且在日程界面 |
| 后置條件 | 添加了新的日程到日歷,對應日期格顯示 marked |
| 正常流程 | 1. 點擊“新增日程”按鈕,打開日程創建面板 2. 填寫事件,設定時間段 3. 選擇是否設定提醒時間 4. 點擊“提交”按鈕 |
| 擴展流程 | 1. 取消創建日程 |
| 特殊要求 | 無 |
| 用例編號 | S1.3 |
| 用例名稱 | 查看考試安排 |
| 參與者 | 學生 |
| 觸發條件 | 點擊“查看考試安排”按鈕 |
| 前置條件 | 學生已經登錄,并且在日程界面 |
| 后置條件 | 顯示顯示本學期所有考試安排 |
| 正常流程 | 1. 點擊“查看考試安排”按鈕,顯示考試安排面板 |
| 擴展流程 | 1. 添加提醒 |
| 特殊要求 | 無 |
活動圖


較為傾向于從用例描述中抽象出類,老師發的資料中也寫到,用例描述占據著皇后的位置,而三王一后中沒有出現活動圖,我在寫完用例描述后對程式也已經有了輪廓,

類圖 <重中之重>
學習博客【深入淺出UML類圖】
從用例描述中,抽象出所有的類,我們先提取物體類,有日程類,以及填充日歷的日期格類,邊界類這里就是界面類,有日程主界面類,添加日程的界面類,兩個界面類分別都有控制類實作相關的業務邏輯,界面類里面的部件主要是日歷類,日程串列類,考試串列類,

先把物體類的屬性寫好,日程類我們很容易可以知道,有日程名稱,開始和截止時間,提醒時間,用戶在填寫事件時,可能還有一些額外的資訊需要提醒自己,那么就添加一個事件備注屬性,
日期格類應該包含的是當前格子表示的日期,因為我們還可以直接看到這個格子是否有日程,應該是抽象為一個狀態,這里我預備用布爾型 isMarked 來表示,(當時寫的時候還加了Mark屬性,作為如果存在日程的標記,多余了,
然后進一步思考類之間的關系,先看聚合關系,ScheduleList 和 ExamList 都是由 Schedule 聚合而來,但 ExamList 屬于特殊的日程,它在這里只能查看不能修改,日期格和日歷Calendar也是聚合關系,我的想法是按月顯示,那關系就是一個日歷中由35個日期格,
考試串列,日程串列和日歷都屬于主界面的部件,最后得到類圖如下

代碼實作
用starUML的正向工程工具根據上面畫好的類圖匯出所有的代碼,然后用 sceneBuilder 開始頁面布局,
主界面我直接用的做平時成績管理系統的界面稍加改動,已經有基本布局和css渲染,添加新日程界面根據用例描述,就是有對日程基本資訊的編輯,然后確認取消按鍵,
下面是靜態初始界面,還沒有實作任何功能只是個UI,


撰寫界面類,主界面繼承 Application 類,添加日程是由主界面按鈕觸發彈出的,暫時不寫,
public class ScheduleStage extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("resource/ScheduleStage.fxml"));
stage.setTitle("Student Schedule");
stage.setScene(new Scene(root, 1080, 720));
stage.setResizable(false);
stage.centerOnScreen();
stage.show();
}
}
開始撰寫代碼,正向工程匯入后已經有了屬性,物體類只需要撰寫setter和getter方法以及構造器

日歷是用GirdPane寫的,每一個日期格都繼承AnchorPane,便于在內部進行布局

兩個 List 部件類
ScheduleList 有 Schedule 的聚合,初始化我們從資料庫匯入資料(因為用到資料庫的地方較少,就不分離出來的(絕不是懶:/,洗掉和修改的方法這里暫時不寫,
ExamList 直接從資料庫初始化資料之后不再會變化,所以它包含的應該是 final static 的 Schedule 陣列,與上面類似就已經完成了,

日歷類 Calendar 和兩個控制元件按鈕的行為
日歷類其中涉及細節較多,這里把它當作已經封裝好的日歷控制元件FXCalendar,根據類圖,我們要完成的時在按上月和下月的按鈕時,日歷要做出變化,
兩個按鈕觸發的行為實作代碼
@FXML
void onButtonLastMonthClicked(ActionEvent event){
LocalDateTime now = LocalDateTime.of(Integer.parseInt(labelYear.getText()), Month.valueOf(labelMonth.getText()), 1, 0, 0, 0);
now = now.minusMonths(1);
labelYear.setText(String.valueOf(now.getYear()));
labelMonth.setText(String.valueOf(now.getMonth()));
changeCalendar(now.getYear(), String.valueOf(labelMonth.getText()));
}
@FXML
void onButtonNextMonthClicked(ActionEvent event){
LocalDateTime now = LocalDateTime.of(Integer.parseInt(labelYear.getText()), Month.valueOf(labelMonth.getText()), 1, 0, 0, 0);
now = now.plusMonths(1);
labelYear.setText(String.valueOf(now.getYear()));
labelMonth.setText(String.valueOf(now.getMonth()));
changeCalendar(now.getYear(), String.valueOf(labelMonth.getText()));
}
可以看到上面呼叫了 changeCalendar() 方法來實作日歷的變化,下面是 changeCalendar() 代碼實作
private void changeCalendar(int year, String month){
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("yyyy MMMM")
.toFormatter(Locale.ENGLISH);
populateDate(YearMonth.parse(year + " " + month, formatter));
selectedMonth = month;
}
兩個界面的互動和添加日程界面完善
在主界面按下添加日程按鍵是觸發新增日程界面資訊,每次都會產生一個新的界面,一個主界面可以有多個添加日程界面,
/*
* 添加新的日程
* */
@FXML
void onButtonAddNewClicked(ActionEvent event) {
Stage addNewStage = new Stage();
Parent root = null;
try {
root = FXMLLoader.load(getClass().getResource("resource/AddNewSchedule.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
addNewStage.initStyle(StageStyle.UNDECORATED);
addNewStage.setTitle("Student");
addNewStage.setScene(new Scene(root, 600, 450));
addNewStage.centerOnScreen();
addNewStage.show();
}
我們從類圖中得知,主要有填入事件資訊,是否需要提醒,確認添加和取消操作,(這里在撰寫的時候就發現,類圖的不足之處,打開提醒應該是由控制類來完成,
填入事件資訊是在界面中完成的
確認添加事件 getNewScheduleButton()
/*
* 將新增的事件放入Schedule
* */
@FXML
void getNewScheduleButton(ActionEvent event) {
String name = itemName.getText();
String comment = itemRemark.getText();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String btime = beginDate.getValue() + " " + beginTime.getValue();
String etime = endDate.getValue() + " " + endTime.getValue();
newSchedule = new Schedule(name, comment, LocalDateTime.parse(btime, dtf), LocalDateTime.parse(etime, dtf));
if(isRemindTogButton.isSelected()) {
newSchedule.setRemind(true);
newSchedule.setReminderTime(LocalDateTime.parse(remindDate.getValue() + " " + remindTime.getValue(), dtf));
}
addNew(newSchedule);
closeAddNewStage(event);
}
確認和取消都會觸發視窗的關閉,而視窗是在主界面控制類生成的,在這里需要獲取當前按鈕所在視窗來關閉
@FXML
void closeAddNewStage(ActionEvent event) {
Stage stage = (Stage) closeButton.getScene().getWindow();
stage.close();
}
是否提醒
/*
* 是否打開提醒
* */
private JFXTimePicker remindTime = new JFXTimePicker();
private JFXDatePicker remindDate = new JFXDatePicker();
@FXML
void isReminding(ActionEvent event) {
boolean isSelected = isRemindTogButton.isSelected();
if(isSelected) {
remindDate.setDefaultColor(Paint.valueOf("#0442bf"));
remindTime.setDefaultColor(Paint.valueOf("#0442bf"));
remindDate.setPrefSize(153, 23);
remindTime.setPrefSize(153, 23);
remindTime.setTranslateY(15);
remindDate.setTranslateY(15);
remindHBox.getChildren().addAll(remindDate, remindTime);
}
else {
remindHBox.getChildren().remove(1, 3);
}
}
到這里,添加日程界面和控制類都完成了
最后要做的是洗掉選中日程和查看選中日程deleteSchedule(in schedule:Schedule),showScheduleDetail(schedule:Schedule)
這里有個十分迷惑的小bug,雖然處理了,但還是不知道為什么,希望有大佬解惑
ClickedID是在監聽日程串列中被滑鼠選中的事件編號,
/*
* 洗掉選中日程
* */
@FXML
void deleteButtonOnAction(ActionEvent event) {
System.out.println(clickedId);
if(clickedId == -1) return;
observableList.remove(clickedId);
// 十分神奇的bug!!!在observableList移除最后一個元素后,clickedId自動從0變成-1,故加下面這句
if(clickedId == -1) clickedId++;
System.out.println(clickedId);
delete(clickedId);
}
查看選中日程時,生成對話框來提示選中日程的所有細節,
/*
* 顯示選擇日程細節
* */
@FXML
void showDetailButtonOnAction(ActionEvent event) {
if(clickedId == -1) return;
Schedule schedule = list.get(clickedId);
JFXAlert alert = new JFXAlert(showDetailButton.getScene().getWindow());
alert.initModality(Modality.APPLICATION_MODAL);
alert.setOverlayClose(false);
JFXDialogLayout layout = new JFXDialogLayout();
Label label = new Label(schedule.getItemName());
label.setFont(new Font("Cambria", 32));
layout.setHeading(label);
Label newContent = new Label("備注: " + schedule.getItemRemark()
+ "\n開始時間: " + schedule.getStartDate()
+ "\n結束時間: " + schedule.getEndDate()
+ "\n提醒時間: " + schedule.getReminderTime());
newContent.setFont(new Font("Cambria", 16));
layout.setBody(newContent);
JFXButton closeButton = new JFXButton("確 認");
closeButton.setPrefSize(150,55);
closeButton.setFont(new Font("Cambria", 16));
closeButton.getStyleClass().add("dialog-accept");
closeButton.setOnAction(e -> alert.hideWithAnimation());
layout.setActions(closeButton);
alert.setContent(layout);
alert.show();
}
list 做出的相應操作

總結心得
從類圖到代碼仍舊花了不少時間在不斷思考如何組織和實作,一度想不管結構全部累在一起,這里類圖起了一個很大的規范作用,它在設計階段,規范好整個框架,讓我先對業務流程有了大致的輪廓,如果感覺有錯誤,可以在類圖階段就修改,而不是等到實作時修改代碼,減小犯錯成本,
其實代碼實作后,我還回傳去修改了類圖
一個是命名規范,當時設計類圖命名比較草率,導致在代碼累積下來后不能見名知意,所以重構了代碼并且修改類圖中類和方法的命名;
第二個是方法的引數和回傳值,類圖設計時我對每個方法都寫了形參和回傳值,但具體實作時大概率會發生變化,比如洗掉日程那只需要傳遞日程ID,而不是把Schedule傳過去;
類圖上的時間安排
其出現兩個問題,其一是在根據類圖實作代碼時發現有些細節沒有考慮到,需要在實作時再花時間來設計,其二是根據類圖的設計無從下手,有我Java功底尚淺的原因,但也可能是因為設計的不合理,
類圖設計的快,可能會有細節被忽略;類圖設計的慢,不斷打磨精細,如果后續要修改,可能因為投入了較多的時間成本不想修改,
這次從建模到實作,識訓很大,真的感受到一個好的建模可以讓整個功能實作更加高效,我的建模可能還很不規范,之后還是多實踐和總結!
本文來自博客園,作者:01kkkrill,轉載請注明原文鏈接:https://www.cnblogs.com/krill/p/15612609.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/374394.html
標籤:其他
上一篇:淺談 DDD 領域驅動設計
