系列前文已闡述「設計即代碼」與「表達性決定可發現性」。本文聚焦:發現慣用模式後,如何將其提取、固化、複用?介紹兩大主流路徑:API 框架化與元程式設計(Annotation/屬性)。
最直觀的複用方式——將重複結構抽取為可呼叫的 API。開源框架本質上皆是慣用模式集:Spring 封裝 DI/IoC 模式,Hibernate 封裝 ORM 模式,Web 框架封裝 MVC 模式。
直接使用 API 常導致「主機對象」到處傳遞,代碼噪音大:
// 噪音版
Car car = new CarImpl();
MarketingDescription desc = new MarketingDescriptionImpl();
desc.setType("Box");
desc.setSubType("Insulated");
desc.setAttribute("length", "50.5");
car.setDescription(desc);
Java 匿名內部類實例初始化器可「攜帶」主機對象:
// 較靜默版
MarketingDescription desc = new MarketingDescriptionImpl() {{
setType("Box");
setSubType("Insulated");
setAttribute("length", "50.5");
}};
原理:雙大括號建立匿名子類,內部 `{ ... }` 為實例初始化塊,在構造函數前執行。代價是產生額外類文件、語法陌生易誤讀。
使用「關於代碼的代碼」表達模式,使模式在視覺與語義上脫穎而出。Java 主要手段:Annotation(註解)。
// 領域對象
public class Country {
private List regions = new ArrayList<>();
private String name;
@MaxLength(length = 10)
public String getName() { return name; }
@Unique(scope = Country.class)
public String getCode() { return code; }
// ...
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxLength { int length() default 0; }
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique { Class scope() default Unique.class; }
// 抽象驗證器
public abstract class Validator {
public void validate(Object obj) throws ValidationException {
for (Method m : obj.getClass().getMethods()) {
if (m.isAnnotationPresent(getAnnotationType())) {
validateMethod(obj, m, m.getAnnotation(getAnnotationType()));
}
}
}
protected abstract Class getAnnotationType();
protected abstract void validateMethod(Object obj, Method m, Annotation ann);
}
// MaxLength 具體實現
public class MaxLengthValidator extends Validator {
@Override protected Class getAnnotationType() { return MaxLength.class; }
@Override protected void validateMethod(Object obj, Method m, Annotation ann) {
if (m.getName().startsWith("get")) {
MaxLength ml = (MaxLength) ann;
String val = (String) m.invoke(obj);
if (val != null && val.length() > ml.length()) {
throw new ValidationException(m.getName() + " exceeds " + ml.length());
}
}
}
}
// Unique 具體實現(跨對象唯一性)
public class UniqueValidator extends Validator {
@Override protected Class getAnnotationType() { return Unique.class; }
@Override protected void validateMethod(Object obj, Method m, Annotation ann) {
Unique u = (Unique) ann;
try {
// 透過 scope 導航取得父對象集合
Method scopeGetter = obj.getClass().getMethod("get" + u.scope().getSimpleName());
Object scopeObj = scopeGetter.invoke(obj);
Method collGetter = scopeObj.getClass().getMethod("get" + obj.getClass().getSimpleName() + "s");
List> siblings = (List>) collGetter.invoke(scopeObj);
Object myVal = m.invoke(obj);
for (Object sib : siblings) {
if (sib != obj && m.invoke(sib).equals(myVal)) {
throw new ValidationException(m.getName() + " not unique in scope");
}
}
} catch (Exception e) { throw new ValidationException(e); }
}
}
Ruby 的「屬性」實為模組混入方法,支援作用域感知與粘性狀態:
# Ruby 測試類
class TestCalculator < Test::Unit::TestCase
extend TestDirectives
acceptance_only # 粘性屬性:影響後續所有方法定義
def test_complex_calculation
assert_equal(4, Calculator.new.complex_calculation)
end
end
# 實現模組
module TestDirectives
def acceptance_only
@acceptance_build = (ENV['BUILD'] == 'ACCEPTANCE')
end
def method_added(name)
remove_method(name) unless @acceptance_build
@acceptance_build = false # 重置粘性狀態
end
end
關鍵差異:Ruby 在解析階段執行 `method_added` hook,動態決定方法是否保留。Java 註解僅在運行時透過反射生效。Ruby 甚至用 Sticky Annotation 實現 `private`/`public` 等作用域修飾符——它們本質是關鍵字,在 Ruby 中只是普通方法。
| 場景 | 推薦路徑 | 理由 |
|---|---|---|
| 團隊以 Java 為主、無元程式經驗 | API 框架化 | 認知負擔低、IDE 支援完善 |
| 橫切關注點多(驗證、安全、事務、審計) | Annotation 元程式 | 聲明式、集中化、零業務侵入 |
| 需 DSL 風格、業務專家可讀 | JRuby/Groovy 內部 DSL | 語法可定製、可執行文檔 |
| 現有框架已提供擴展點(如 Spring AOP、Hibernate Interceptor) | 框架原生擴展 | 避免重複造輪子、利用容器生命週期 |
下一期將探討:領域特定語言(DSL)作為慣用模式的終極捕捉形式——從外部 DSL(ANTLR)到內部 DSL(Groovy/Ruby),如何讓代碼直接「說業務語言」。