開源項(xiàng)目YtyMark文本編輯器--UI界面相關(guān)功能(關(guān)于設(shè)計(jì)模式的實(shí)戰(zhàn)運(yùn)用)
??開源項(xiàng)目地址
歡迎提交 PR、Issue、Star ??!
??1. 簡(jiǎn)述
YtyMark-java項(xiàng)目分為兩大模塊:
-
UI界面(ytyedit-mark)
-
markdown文本解析和渲染(ytymark)
本文主要內(nèi)容為UI界面相關(guān)功能。
關(guān)于markdown文本解析器UI界面的實(shí)現(xiàn)。在這整個(gè)流程中,如果通過(guò)設(shè)計(jì)模式實(shí)現(xiàn)高內(nèi)聚低耦合,可重用,易于閱讀,易于擴(kuò)展,易于維護(hù)等。
YtyMark-java
├── ytyedit-mark/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ ├── editor/ # JavaFX UI 界面
│ │ │ │ ├── enums/ # Icon圖標(biāo)等
│ │ │ │ ├── utils/ # 資源讀取等
│ │ │ │ ├── window/ # 自定義窗口(主窗口、彈框)
│ │ │ │ ├── RenderMarkdown # 解析和渲染
│ │ │ │ ├── YtyEditApplication # 主程序入口
│ │ │ └── resources/
│ │ │ └── css/ # 主題樣式(CSS)
│ │ │ └── fonts/ # 字體集
│ │ │ └── images/ # 圖片
│ ├── README.md
│ └── pom.xml
??2. JavaFX 用戶界面
目標(biāo):為用戶提供可視化的文本輸入、實(shí)時(shí)預(yù)覽、編輯、保存、導(dǎo)出(PDF/HTML)和主題切換等功能。
使用到的設(shè)計(jì)模式:
-
工廠模式:樣式的創(chuàng)建通過(guò)工廠模式來(lái)完成。
-
策略模式:動(dòng)態(tài)選擇工具界面樣式,完成UI界面的樣式切換。
-
觀察者模式:識(shí)別到主題發(fā)生變化時(shí)執(zhí)行重新渲染操作;樣式切換后,渲染的文字樣式也需要同步調(diào)整,再結(jié)合監(jiān)聽器(觀察者模式)來(lái)實(shí)現(xiàn)主題變化后重新渲染文本內(nèi)容,除此之外JavaFX使用了大量的監(jiān)聽器。
-
單例模式:主題樣式管理器統(tǒng)一管理全局樣式,并提供統(tǒng)一訪問(wèn)入口。
-
裝飾模式:對(duì)自定義基礎(chǔ)彈框做定制化的擴(kuò)展,實(shí)現(xiàn)不同場(chǎng)景所需的彈框。
-
命令模式:封裝工具界面中的功能點(diǎn)及快捷鍵命令。
-
備忘錄模式:負(fù)責(zé)實(shí)現(xiàn)撤銷和恢復(fù)功能,實(shí)現(xiàn)精細(xì)到單字符的撤銷/恢復(fù)機(jī)制。
2.1. 工廠模式
通過(guò)工廠創(chuàng)建樣式,將默認(rèn)的樣式創(chuàng)建好,統(tǒng)一放入Map中保存,需要時(shí)直接從工廠中獲取。
public class StyleFactory {
private static final Map<Key, Style> STYLE_MAP = new HashMap<>();
// 預(yù)先注冊(cè)默認(rèn)樣式
static {
registerStyle(WindowType.MAIN_WINDOW, ThemeType.LIGHT, new MainWindowLightStyle());
registerStyle(WindowType.MAIN_WINDOW, ThemeType.DARK, new MainWindowDarkStyle());
registerStyle(WindowType.DIALOG_WINDOW, ThemeType.LIGHT, new DialogWindowLightStyle());
registerStyle(WindowType.DIALOG_WINDOW, ThemeType.DARK, new DialogWindowDarkStyle());
}
public static void registerStyle(WindowType type, ThemeType theme, Style style) {
STYLE_MAP.put(new Key(type, theme), style);
}
public static Style getStyle(WindowType type, ThemeType theme) {
Style style = STYLE_MAP.get(new Key(type, theme));
if (style == null) {
throw new RuntimeException("窗口類型或主題類型不支持: " + type + " - " + theme);
}
return style;
}
...
}
2.2. 策略模式
動(dòng)態(tài)選擇工具界面樣式,完成UI界面的樣式切換。并支持對(duì)已有窗口樣式的自由組合,比如深色的主窗口+淺色的彈框。
使用setTheme(ThemeType)方法可以快速切換已經(jīng)搭配好的主題;使用setStyle可以靈活指定不同窗口的樣式。
public class ThemeContext {
...
// 設(shè)置樣式
public void setTheme(ThemeType theme) {
WindowType[] values = WindowType.values();
for (WindowType windowType : values) {
this.themeManager.setStyle(windowType, theme);
}
}
// 自定義設(shè)置不同窗體不同樣式
public void setStyle(WindowType type, ThemeType theme) {
this.themeManager.setStyle(type, theme);
}
// 主題切換
public void switchTheme() {
// 清空之前的樣式
scene.getStylesheets().clear();
this.themeManager.applyStyle(WindowType.MAIN_WINDOW, scene);
}
}
2.3. 觀察者模式和單例模式
識(shí)別到主題發(fā)生變化時(shí)執(zhí)行重新渲染操作。樣式切換后,渲染的文字樣式也需要同步調(diào)整,結(jié)合監(jiān)聽器(觀察者模式)來(lái)實(shí)現(xiàn)主題變化后重新渲染文本內(nèi)容,除此之外JavaFX使用了大量的監(jiān)聽器。
自定義的主題監(jiān)聽器接口:
public interface ThemeChangeListener {
void onThemeChanged();
}
主題監(jiān)聽器將有主題管理類來(lái)統(tǒng)一管理,并結(jié)合單例模式,使得主題管理器全局唯一,并提供統(tǒng)一訪問(wèn)入口ThemeManager.getInstance()。
public class ThemeManager {
private static ThemeManager instance;
private final Map<WindowType, Style> currentStyles = new HashMap<>();
private final List<ThemeChangeListener> listeners = new ArrayList<>();
private ThemeManager() {
// 默認(rèn)主窗口白天模式,彈窗白色模式
currentStyles.put(WindowType.MAIN_WINDOW, new MainWindowLightStyle());
currentStyles.put(WindowType.DIALOG_WINDOW, new DialogWindowLightStyle());
}
public static ThemeManager getInstance() {
if (instance == null) {
instance = new ThemeManager();
}
return instance;
}
public void setStyle(WindowType type, ThemeType theme) {
currentStyles.put(type, StyleFactory.getStyle(type, theme));
// 通知所有觀察者主題已變更
this.notifyThemeChanged();
}
...
// 注冊(cè)觀察者
public void addThemeChangeListener(ThemeChangeListener listener) {
listeners.add(listener);
}
// 注銷觀察者
public void removeThemeChangeListener(ThemeChangeListener listener) {
listeners.remove(listener);
}
// 通知所有觀察者
private void notifyThemeChanged() {
for (ThemeChangeListener listener : listeners) {
listener.onThemeChanged();
}
}
}
通用彈框和渲染處理類實(shí)現(xiàn)了監(jiān)聽器接口,在創(chuàng)建時(shí)注冊(cè)到監(jiān)聽器中
public class GenericDialog implements ThemeChangeListener {
public GenericDialog(Stage owner) {
...
this.themeManager.addThemeChangeListener(this);
...
}
/**
* 訂閱主題樣式變更,主題變更后自動(dòng)切換彈窗樣式
*/
@Override
public void onThemeChanged() {
// 清空之前的樣式
dialogScene.getStylesheets().clear();
themeManager.applyStyle(WindowType.DIALOG_WINDOW,dialogScene);
}
}
渲染處理類監(jiān)聽相關(guān)源碼
public class RenderMarkdown implements ThemeChangeListener {
public RenderMarkdown(Tab tab) {
...
// 注冊(cè)自己為 ThemeContext 的監(jiān)聽者
ThemeManager.getInstance().addThemeChangeListener(this);
...
}
/**
* 訂閱主題樣式變更,主題變更后自動(dòng)重新渲染內(nèi)容
*/
@Override
public void onThemeChanged() {
// 主題變更后自動(dòng)渲染
renderMarkdown(null);
}
}
2.4. 裝飾模式
對(duì)自定義基礎(chǔ)彈框做定制化的擴(kuò)展,實(shí)現(xiàn)不同場(chǎng)景所需的彈框。彈框裝飾類通過(guò)組合的方式,對(duì)通用彈框做定制化設(shè)置
public abstract class DialogDecorator {
private GenericDialog dialog;
public DialogDecorator(GenericDialog dialog) {
this.dialog = dialog;
}
public void removeLogo(boolean flag){
dialog.removeLogo(flag);
}
// 委托設(shè)置標(biāo)題
public void setTitle(String title) {
dialog.setTitle(title);
}
// 委托設(shè)置主體內(nèi)容
public void setContent(Node content) {
dialog.setContent(content);
}
// 委托添加內(nèi)容
public void addContent(Node node) {
dialog.addContent(node);
}
// 委托設(shè)置底部按鈕區(qū)域
public void setFooter(Node node) {
dialog.setFooter(node);
}
// 委托添加底部按鈕
public void addFooter(Node node) {
dialog.addFooter(node);
}
// 顯示并阻塞,返回用戶操作結(jié)果
public void showAndWait() {
dialog.showAndWait();
}
}
2.5. 命令模式和備忘錄模式
封裝工具界面中的功能點(diǎn)及快捷鍵命令。備忘錄模式負(fù)責(zé)實(shí)現(xiàn)撤銷和恢復(fù)功能,實(shí)現(xiàn)精細(xì)到單字符的撤銷/恢復(fù)機(jī)制,主要涉及的類:管理文本狀態(tài)TextEditorOriginator、管理撤銷和恢復(fù)的棧UndoRedoCaretaker。
public class UndoRedoCaretaker {
private Deque<TextMemento> undoStack = new ArrayDeque<>();
private Deque<TextMemento> redoStack = new ArrayDeque<>();
private TextEditorOriginator originator;
private boolean isUndoRedo = false;
private static final int MAX_HISTORY = 1000; // 最多保存 1000 步
public UndoRedoCaretaker(TextEditorOriginator originator) {
this.originator = originator;
// 存入初始狀態(tài),確保撤銷可用
undoStack.push(originator.save());
}
// 保存
public void saveState(String text, int caretPosition) {
...
}
// 撤銷
public void undo() {
...
}
// 恢復(fù)
public void redo() {
...
}
public boolean isUndoRedo() {
return isUndoRedo;
}
public void clear() {
undoStack.clear();
redoStack.clear();
}
}
?? 3. 界面截圖預(yù)覽
白天模式的截圖:

夜間模式的截圖:

??4. 總結(jié)
YtyMark 編輯器界面UI相關(guān)功能,將多種設(shè)計(jì)模式融入到實(shí)際應(yīng)用中,從實(shí)踐中積累程序設(shè)計(jì)經(jīng)驗(yàn)。
更多詳細(xì)內(nèi)容可以前往筆者微信公眾號(hào)回復(fù):設(shè)計(jì)模式,來(lái)獲取,后續(xù)有關(guān)設(shè)計(jì)模式的新資料都可以從這個(gè)入口獲取到。
-
秘籍1設(shè)計(jì)模式手冊(cè):《掌握設(shè)計(jì)模式:23種經(jīng)典模式實(shí)踐、選擇、價(jià)值與思想》
-
秘籍2練手項(xiàng)目:設(shè)計(jì)模式實(shí)戰(zhàn)項(xiàng)目--markdown文本編輯器軟件開發(fā)(已開源)

查看往期設(shè)計(jì)模式文章的:設(shè)計(jì)模式
超實(shí)用的SpringAOP實(shí)戰(zhàn)之日志記錄
通過(guò)軟考后卻領(lǐng)取不到實(shí)體證書?
計(jì)算機(jī)算法設(shè)計(jì)與分析(第5版)
Java全棧學(xué)習(xí)路線、學(xué)習(xí)資源和面試題一條龍
軟考中級(jí)--軟件設(shè)計(jì)師毫無(wú)保留的備考分享
三連支持!!!

浙公網(wǎng)安備 33010602011771號(hào)