public class TestExportable {
public static void main(String[] args) {
// 建構員工物件圖(含巢狀成員)
Regular emp = new Regular("Catherine", "IBM", "Software Engineer", "82888288", "BJ", new Date());
emp.addMember(new Vendor("Steve", "IBM", "PVC", "8"));
emp.addMember(new Vendor("Steve", "IBM", "PVC", "8"));
emp.addMember(new Contractor("Joe", "IBM"));
emp.addMember(new Supplemental("Linda", "IBM", "8"));
try (PrintWriter pw = new PrintWriter(new FileOutputStream("C:\\test.output", true))) {
ExportableGenerator gen = new TXTExportableGenerator(pw);
gen.genDoc(emp, 0);
gen.flush();
} catch (FileNotFoundException e) { e.printStackTrace(); }
}
}
public abstract class ExportableGenerator {
protected PrintWriter out;
public ExportableGenerator(PrintWriter out) { this.out = out; }
// 主入口:遞迴處理物件圖
public void genDoc(Employee e, int tagNum) {
Class<?> clazz = e.getClass();
Field[] fields = clazz.getDeclaredFields();
outputFieldHeader(out, e);
for (Field f : fields) {
if (f.isAnnotationPresent(Exportable.class)) {
if (f.getType() != ArrayList.class) {
// 簡單欄位:輸出縮排 + 欄位內容
for (int i = 0; i < tagNum; i++) out.print("***");
outputSimpleField(out, f, e);
} else {
// 集合欄位:遞迴處理每個成員
try {
@SuppressWarnings("unchecked")
ArrayList<Employee> team = (ArrayList<Employee>) f.get(e);
out.println("-----------------------------");
for (Employee member : team) {
genDoc(member, tagNum + 1);
out.println("-----------------------------");
}
} catch (IllegalAccessException ex) { ex.printStackTrace(); }
}
}
}
outputFieldFooter(out, e);
}
public void flush() {
out.flush();
out.close();
}
// 型別安全的值提取與格式化
protected String value(Field f, Object obj) {
Class<?> type = f.getType();
try {
if (type == String.class) return (String) f.get(obj);
if (type == Date.class) return DateFormat.getDateInstance().format((Date) f.get(obj));
} catch (IllegalAccessException e) { e.printStackTrace(); }
return f.getName(); // fallback
}
// 留給子類別實作的三個擴充點
protected abstract void outputSimpleField(PrintWriter out, Field f, Object obj);
protected abstract void outputFieldHeader(PrintWriter out, Object e);
protected abstract void outputFieldFooter(PrintWriter out, Object e);
}
| 模式 | 體現位置 | 效益 |
|---|---|---|
| Template Method | genDoc 定義遞迴骨架,outputSimpleField 等延遲到子類 | 演算法骨架固定,格式細節可變 |
| Reflection + Annotation | f.isAnnotationPresent(Exportable.class) | 零硬編碼欄位名,新增欄位只加註解 |
| Visitor 虛擬化 | 遞迴 genDoc 替代雙重分派 | 無需修改 Employee 階層即可新增匯出格式 |
| Strategy | TXTExportableGenerator / XMLExportableGenerator | 格式切換僅需注入不同 Generator |
getDeclaredFields() 取得私有欄位,需配合 f.setAccessible(true)(此處省略,實際專案必加)。ArrayList 執行期無型別參數,用 f.getType() != ArrayList.class 判斷集合欄位。tagNum 參數追蹤遞迴深度,產生視覺層次。DateFormat.getDateInstance() 依區域預設格式化。
public class XMLExportableGenerator extends ExportableGenerator {
public XMLExportableGenerator(PrintWriter out) { super(out); }
@Override protected void outputFieldHeader(PrintWriter out, Object e) {
out.println("<" + e.getClass().getSimpleName() + ">");
}
@Override protected void outputFieldFooter(PrintWriter out, Object e) {
out.println("" + e.getClass().getSimpleName() + ">");
}
@Override protected void outputSimpleField(PrintWriter out, Field f, Object obj) {
out.print(" <" + f.getName() + ">");
out.print(value(f, obj));
out.println("" + f.getName() + ">");
}
}
僅 15 行代碼即實現完整 XML 匯出,展現開放封閉原則威力。
Ctrl+1 → Implement abstract methods 一鍵生成三個 output... 存根。(ArrayList<Employee>) 附帶 @SuppressWarnings("unchecked"),Eclipse 自動建議加上。Field. 顯示 isAnnotationPresent、getType、get 等關鍵方法。ExportableGenerator 框架以反射掃描註解 + 模板方法遞迴 + 策略模式格式化三重模式結合,實現「欄位級宣告、格式零耦合、結構自動遞迴」的通用匯出引擎。測試代碼展示完整物件圖建構與輸出驗證流程,為後續 XML/JSON/CSV 多格式擴充奠定堅實基礎。