首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

設計可擴展應用程式(二):透明箱擴展性——訪問者模式與可擴展訪問者模式實戰

     編輯:Java 相關知識

透明箱擴展性:源代碼可查看不可修改,介於黑箱(不看代碼)與開放箱(直接改代碼)之間,或許是擴展系統最有效、最安全的方法。本文詳述透明箱擴展性兩大維度:資料擴展與功能擴展。

資料維度擴展:類別層次結構

面向對象語言天然支援資料擴展。例:二元樹類別層次:


abstract class Tree {}
class Branch extends Tree { int value; Tree left, right; ... }
class Leaf extends Tree {}

新增非空葉節點只需定義 NonEmptyLeaf extends Tree,無需動既有代碼。

功能維度擴展:訪問者模式

若需易於擴展功能而非子類型,採用訪問者模式。為復合資料型別定義抽象訪問者:


interface TreeVisitor {
  Object forBranch(Branch that);
  Object forLeaf(Leaf that);
}

每個子類型含 accept(TreeVisitor) 調用對應 forXxx。新增功能(如深度複製)只需實作新 TreeVisitor 子類:


class TreeCopier implements TreeVisitor {
  Object forBranch(Branch that) {
    return new Branch(that.getValue(), (Tree)that.getLeft().accept(this), (Tree)that.getRight().accept(this));
  }
  Object forLeaf(Leaf that) { return new Leaf(); }
}

訪問者模式的麻煩:雙向擴展衝突

若同時擴展資料子類型(新增 NonEmptyLeaf),現有 TreeVisitor 缺 forNonEmptyLeaf。解決:建立 TreeVisitor2 extends TreeVisitor 加新方法,NonEmptyLeaf.accept 強制轉型調用。但訪問者構造新訪問者時(如美化列印追蹤縮進),子類繼承父類 forBranch 仍構造父類型訪問者,導致運行時轉型失敗:


class TreePrinter2 extends TreePrinter implements TreeVisitor2 {
  // 繼承的 forBranch 仍構造 TreePrinter 而非 TreePrinter2!
  TreePrinter innerPrinter = new TreePrinter(this.amountOfIndentation + 1, this.out); // 錯誤
}

解決方案:可擴展訪問者模式(工廠方法)

Krishnamurthi 等人提議:以工廠方法取代構造器建立新訪問者實例,子類覆寫工廠方法回傳正確類型:


// TreePrinter
TreePrinter newTree(int indent, OutputStream out) { return new TreePrinter(indent, out); }
Object forBranch(Branch that) {
  TreePrinter innerPrinter = newTree(this.amountOfIndentation + 1, this.out); // 呼叫工廠方法
  ...
}

// TreePrinter2 覆寫
TreePrinter newTree(int indent, OutputStream out) { return new TreePrinter2(indent, out); }

此即可擴展訪問者模式,同時支援資料與功能雙向擴展。

結語:性能與擴展性的權衡

此設計付出性能代價:遞歸調用昂貴,大型結構易堆疊溢出。JIT 動態內聯與 IBM 尾調用消除緩解此問題。實際上,最需擴展性的部件通常非效能關鍵路徑。下期討論黑箱擴展性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved