Java Swing 等傳統 UI 框架雖功能強大,但開發者常陷入「座標地獄」:設計師腦海中清晰的「按比例縮放」、「居中對齊」、「網格排列」等高層佈局意圖,被迫拆解為大量 setBounds(x, y, w, h)、GridBagConstraints 等低層細節。這導致代碼難讀、難改、難復用——一個簡單的「所有按鈕留白 2%」需求,可能引發數百行座標修改。
現有解決方案皆有短板:
BorderLayout、GridBagLayout 等僅覆蓋常見模式,複雜佈局仍需嵌套面板、手寫約束,擴展性極弱。我們不將設計意圖「降級」為實現細節,而是提供一種直接描述高層佈局樣式的語言。通過這種語言,開發者可:
所有佈局單元實現 Component 介面:
public interface Component {
Component at(int x, int y, int width, int height); // 指定佔據矩形
Component in(Container container); // 放入容器
}
基本實現如 Button、TextField 等封裝 Swing 組件;特殊原子 Empty 僅佔位不渲染。
兩大核心組合操作,均接受兩個 Component 與一個比例 float ratio,返回新的 Component(閉包性):
beside(left, right, ratio):水平分割,左佔 ratio,右佔 1-ratio。above(top, bottom, ratio):垂直分割,上佔 ratio,下佔 1-ratio。實現關鍵:組合子本身實現 Component,其 at() 方法遞歸計算子組件矩形並委派。
// 置中佈局:水平/垂直留白比例
public Component center(Component cp, float hRatio, float vRatio) {
float s1 = (1 - 2*hRatio) / (1 - hRatio);
float s2 = (1 - 2*vRatio) / (1 - vRatio);
Component u = above(Empty(), above(cp, Empty(), s2), vRatio);
return beside(Empty(), beside(u, Empty(), s1), hRatio);
}
// 水平/垂直序列(等比分配)
public Component h_seq(Component[] cps) {
if (cps.length == 1) return cps[0];
return beside(cps[0], h_seq(Arrays.copyOfRange(cps, 1, cps.length)), 1.0/cps.length);
}
public Component v_seq(Component[] cps) { /* 類似 */ }
// N 行 M 列網格
public Component block(Component[] cps, int N, int M) {
Component[][] grid = formalize(cps, N, M); // 不足補 Empty
Component[] rows = new Component[N];
for (int i = 0; i < N; i++) rows[i] = h_seq(grid[i]);
return v_seq(rows);
}
// 帶邊距網格:復用 center 樣式
public Component block_with_margin(Component[] cps, int N, int M, float hRatio, float vRatio) {
Component[] wrapped = Arrays.stream(cps)
.map(cp -> center(cp, hRatio, vRatio))
.toArray(Component[]::new);
return block(wrapped, N, M);
}
Component[] keys = { btn("0"), btn("1"), ..., btn("/") }; // 16 個按鈕
Component opLayout = block(keys, 4, 4); // 4x4 網格
// 整體:頂部輸入框 + 退格/清除 + 按鈕區
above(
above(textField(), beside(btn("Backspace"), btn("C"), 0.5), 0.5),
opLayout,
0.3
).at(0, 0, 300, 200).in(frame.getContentPane());
// 需求變更:所有按鈕留白 2% → 僅改一行
Component opLayout = block_with_margin(keys, 4, 4, 0.02, 0.02);
這就是 DSL 的威力:需求變更直接對應語言層面的微調,而非實現層的大規模重寫。
JButton, JPanel 等)。Component, beside, above, Empty —— 純佈局幾何,無業務邏輯。center, h_seq, block 等高階樣式,以 Java 方法形式定義,可無限擴展。// Fluent Interface 風格:鏈式調用返回 this
public class Button implements Component {
private JButton btn = new JButton();
public Component title(String t) { btn.setText(t); return this; }
public Component at(int x, int y, int w, int h) { btn.setBounds(x,y,w,h); return this; }
public Component in(Container p) { p.add(btn); return this; }
}
// beside 組合子:遞歸分割矩形
public class Beside implements Component {
private Component left, right; private float ratio;
public Beside(Component l, Component r, float ratio) { this.left=l; this.right=r; this.ratio=ratio; }
public Component at(int x, int y, int w, int h) {
left.at(x, y, (int)(w*ratio), h);
right.at(x + (int)(w*ratio), y, w - (int)(w*ratio), h);
return this;
}
public Component in(Container p) { left.in(p); right.in(p); return this; }
}
為連接後端邏輯,引入「擁有者」概念:
Button().title("Submit").ownBy(submitController);
// 所有事件處理在 submitController 中,佈局代碼零業務邏輯
這實現了 Model-View-Presenter 的變體,佈局 DSL 專注 View 結構描述。
界面佈局 DSL 不僅解決了 Swing 開發的痛點,其設計思想——識別語義斷層、構建分層 DSL、採用 Fluent Interface、實現閉包組合子、分離顯示與控制——對於任何存在「高層意圖 vs 低層實現」斷層的領域(如報表生成、工作流定義、遊戲關卡設計)皆具備指導意義。嘗試在專案中引入或自建 DSL,將編程體驗從「搬磚」提升為「寫詩」。