1 介紹
重構在現代軟件開發過程中扮演著重要的角色,它能夠減輕軟件開發人員的 工作負擔,提高軟件開發的生產效率。為了闡明重構的重要性,我們在這裡引用 了developerWorks上David Carew提供的關於重構的教程中的一段話:
現在,一個開發者的工作大部分在於對現有的代碼進行修改,而不是起草寫 新的代碼。簡單的修改可能包括對現有代碼進行添加。然而,多樣化的修改或擴 展的改變會使軟件內部結構開始惡化。重構改變軟件的內部結構使得軟件更容易 理解並且在不需要改變其顯著的行為的情況下使得修改的代價也更小。
在Java軟件開發過程中,通過使用Eclipse提供的重構工具,我們至少獲得了 以下好處:
1. 最終產品更為健壯:我們對程序代碼的修改將不太可能出錯,出現遺漏修 改的可能變少,即使出現問題也能夠通過Undo功能回退到重構前的狀態。
2. 提高了生產效率。通常一次重構能夠完成對程序代碼的多處改動。最為明 顯的例子可能是Eclipse提供的Rename重構,它能夠在修改名稱的同時相應的更 改所有的引用。
Eclipse 為我們提供了多種實用的重構功能,在軟件開發過程中使用這些重 構能夠給我們帶來極大的好處。然而,針對每個開發人員的特殊需要,總有一些 迫切需要的功能是不能通過已有的重構來獲得的。這個時候,我們可以對 Eclipse平台進行一些擴展,創建適應我們自己需要的重構。如果這個重構恰好 能夠符合大多數人的需要,我們也可以像其他Eclipse的contributor一樣,將我 們的重構貢獻給Eclipse社區。
接下來,我們將通過一個例子來展示如何在Eclipse中創建新的重構功能。我 們這裡創建的重構將用於遷移JUnit的測試用例。我們知道,在當前版本的 JUnit中,一個用於測試的函數必須以字符串"test"作為方法名稱的開始。而在 即將來到的JUnit 4中,一個"@Test"的Annotation被用於標明方法是一個測試方 法。我們將要創建的重構將完成這個遷移工作,即在所有的以"test"開始的方法 之前加上"@Test"標記。@Test Annotation還可以包含一個timeout屬性用來規定 方法的最大執行時間,我們在向導中提供了一個頁面供用戶選擇是否需要 timeout屬性。
2 結果預覽
為了給讀者一個直觀的感受,我們下面首先介紹本文中例子的實際運行效果 。在閱讀完本文之後,讀者朋友也能夠順利的完成類似的功能。
啟動例子程序提供的Refactor之後,我們獲得了一個由三個頁面組成的向導 。在第一個頁面中,用戶可以選擇是否需要timeout參數,並且用戶能夠設置 timeout參數的值。
圖 1 輸入參數
當用戶輸入參數完畢之後,通過單擊Next按鈕我們將進入下一個頁面。向導 將進行初始條件檢查和最終條件檢查,並將檢查的結果反饋給用戶。在圖 2中我 們可以看到,初始條件和最終條件都正常,因此我們可以進入下一步。
圖 2 顯示條件檢查
接下來是預覽窗口(圖 3),向導用直觀的界面顯示了在應用向導之後,我 們將會對源代碼造成怎樣的改動。用戶可以在這個頁面中判斷最終的修改是否符 合自己的需要。另外,用戶也能夠選擇性的取消對某些文件的修改。
當用戶檢查預覽頁面確認沒有問題之後,用戶可以按下Finish按鈕從而完成 重構。這個時候,源代碼會發生修改,最後的結果如下所示:
清單 1
package main;
public class TestSomething {
@Test(timeout=500)
public void testSomething(){
}
}
3 總體結構和流程
在Eclipse中,一個重構操作主要由以下三個部分組成:
1. RefactoringWizard類:RefactoringWizard提供了向導式的用戶界面來引 導用戶完成重構工作。不需要我們做任何工作,Eclipse已經通過 RefactoringWizard為我們提供了預覽頁面、條件檢查頁面以及Undo/Redo等功能 。我們需要繼承這個類從而為重構過程提供特定的用戶界面。
2. Refactoring類:Refactoring類完成具體的定位和修改代碼功能。為了建 立新的Refactoring,我們需要繼承這個類並實現重構的邏輯部分。
3. AST和ASTParser:在Refactoring類中,我們需要對代碼進行定位和修改 ,這可以通過AST機制來完成。AST是abstract syntax tree的簡稱,它能夠將 Java代碼解析成為一個樹形結構。在利用了AST樹之後,對源代碼的修改變成了 對AST樹的遍歷、更改節點屬性,以及插入和刪除節點等。
一個典型的重構操作流程如下所示:
1. 用戶選擇要進行重構的對象,通過菜單項或按鈕啟動重構操作。
2. 創建具體的Refactoring類,彈出RefactoringWizard。
3. RefactoringWizard與用戶交互,引導用戶輸入必要的參數; RefactoringWizard調用Refactoring類的函數進行條件檢查。
4. Refactoring類創建AST,並利用其對源代碼進行定位和修改。這裡進行的 修改並不直接應用到源代碼上,而是被保存成Change對象,供Refactoring框架 使用。
5. RefactoringWizard調用Refactoring類的函數,獲得重構內容的詳細描述 信息(即第4步生成的Change對象),顯示在預覽界面上,待用戶確認。
6. 用戶確認後Refactoring框架將修改代碼,重構操作結束。
接下來,我們將詳細介紹新建重構類型的各個步驟。
4 創建插件工程
在大家對整個系統構架有了一個大概的了解之後,我們的介紹就從創建工程 開始。大家都知道Eclipse提供了很好的擴展性,通過創建插件就能把我們要添 加的重構功能無縫的插入到Eclipse平台中。創建插件工程的方法在很多地方都 有介紹,這裡不再詳細講解。如果需要基礎的插件開發知識,我們可以參考《 開發 Eclipse 插件》,樹立基本的插件開發意識。
通過菜單 File -> New-> Project,選擇Plug-in Project。點擊Next ,出現對話框,輸入項目名稱manage.annotation,接受其他選項的默認值。點 擊Next,出現插件屬性設置的對話框,繼續接受默認值。點擊Next,出現選擇插 件模板對話框,該工程要在Refactor菜單中添加一個新的菜單項,所以這裡我們 采用"Hello,World"的插件模板。點擊Next,修改"Action類名稱"的值為 AnnotationManageAction,點擊 Finish按鈕。至此,一個最基本Eclipse工作台 的插件工程就被創建出來了。
插件工程創建後,缺省進入Plug-in開發透視圖,Plug-in Manifest編輯器自 動打開,顯示這個插件工程的基本信息,如對其他插件的依賴,擴展點,構建 (build)的配置信息等等。由於該工程需要用到其他插件的功能,必須為其添加 到其他插件的依賴。在Plug-in Manifest編輯器點擊Dependencies頁面,在該頁 面中的Required Plug-ins列表中通過Add按鈕添加如下的插件:
清單 2
org.eclipse.jface.text
org.eclipse.ltk.core.refactoring
org.eclipse.ltk.ui.refactoring
org.eclipse.jdt
org.eclipse.jdt.core
或者也可以通過直接修改MANIFEST.MF文件完成。操作完成後察看 MANIFEST.MF文件,注意Require-Bundle列表中是否出現了新添加的這幾項。 MANIFEST.MF文件如下:
清單 3
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Annotation Plug-in
Bundle-SymbolicName: manage.annotation; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: manage.annotation.AnnotationPlugin
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.jface.text,
org.eclipse.ltk.core.refactoring,
org.eclipse.ltk.ui.refactoring,
org.eclipse.jdt,
org.eclipse.jdt.core
Eclipse-AutoStart: true
在Plug-in Manifest編輯器中打開插件清單文件plugin.xml,可以看到,這 個插件擴展了org.eclipse.ui.actionSets擴展點,這是一個基本的Eclipse工作 台的擴展點,通過擴展它,插件可以很簡單得對Eclipse的菜單、工具條進行擴 展。這個plugin.xml 是"Hello,World"插件模板的清單文件,我們把它改成適合 這個工程的文件。清單如下:
清單 4
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
label="Annotation Action Set"
visible="true"
id="manage.annotation.actionSet">
<menu
label="%Refactoring.menu.label"
path="source"
id="org.eclipse.jdt.ui.refactoring.menu">
<separator name="reorgGroup"/>
</menu>
<action
class="manage.annotation.actions.AnnotationManageAction"
icon="icons/sample.gif"
id="manage.annotation.actions.AnnotationManageAction"
label="%Annotation.manage"
menubarPath="org.eclipse.jdt.ui.refactoring.menu/reorgGroup"
toolbarPath="reorgGroup"
tooltip="Manage Annotation in Java Project"/>
</actionSet>
</extension>
</plugin>
該清單文件表明,在Refactor菜單中添加了一個新菜單項"Annotation Manage",並在工具條上相應添加了一個按鈕。點擊菜單項或者按鈕的事件由 類"manage.annotation.actions.AnnotationManageAction"處理。
最後需要修改的就是manage.annotation.actions.AnnotationManageAction 類。它繼承了 org.eclipse.ui.IWorkbenchWindowActionDelegate接口,該接口 用於處理各種通過擴展點添加的操作。當菜單項或者按鈕被點擊時,這個類就被 Eclipse工作台裝載進來,處理轉發過來的請求以及接下來的操作。
AnnotationManageAction 被創建後,一旦用戶的選擇部分有所改變,接口的 selectionChanged函數就會被觸發,告知用戶所選擇的部分,可以在這個函數中 根據用戶的選擇相應的修改操作的可用性或者其他顯示屬性。例如在本文的工程 中,我們希望只有當用戶選擇了一個Java模型元素時才能使用這個操作,那麼就 需要在 selectionChanged中添加如下的代碼:
清單 5
public void selectionChanged(IAction action, ISelection selection) {
if (selection.isEmpty())
select = null;
else if (selection instanceof IStructuredSelection) {
IStructuredSelection strut = ((IStructuredSelection) selection);
if (strut.size() != 1)
select = null;
if (strut.getFirstElement() instanceof IJavaElement)
select = (IJavaElement) strut.getFirstElement();
} else
select = null;
action.setEnabled(select != null);
}
selectionChanged 函數的參數selection記錄了用戶選擇的部分,我們首先 判斷它的選擇部分的數目是否為一,然後判斷這個唯一的選擇部分是否為Java模 型元素,這兩個條件任何一個不滿足都會導致action.setEnabled(false)的執行 ,這時會彈出如下的對話框說明操作不可用,同時菜單項和按鈕都會顯示成灰色 ,直到用戶選擇了合適的部分時,菜單項和按鈕才會實顯,就可以進行具體的操 作了。
圖 4 表明Action目前不能執行的對話框
操作的執行是在AnnotationManageAction的run函數中實現的,例如在本文的 工程中,就是彈出RefactoringWizard對話框,指導用戶完成重構的工作,這些 我們將在下面的章節中講述。
5 擴展Refactoring類
通過前面系統構架的介紹,大家知道了Refactoring和RefactoringWizard是 完成EClipse重構功能的基礎類。在創建好插件工程後,我們就通過擴展 Refactoring來實現具體的功能。
Refactoring是所有支持代碼轉化的類的抽象父類,它在整個流程中與 RefactoringWizard交互以完成重構的功能,起著非常重要的作用。這些類需要 提供以下兩類方法:
用於條件檢查的方法,判斷重構操作大體而言能否執行,以及具體的轉化能 否完成;
創建Change對象的方法,Change對象描述了所有將要執行的對當前代碼的修 改操作。
Refactoring類的典型流程如下所示:
1. 具體的Refactoring類被創建。
2. 獲得用戶選擇的要進行重構的對象,初始化該Refactoring類。這個由具 體的實現類給出相應的方法。
3. 在重構操作開始執行時,首先調用Refactoring的 checkInitialConditions(IProgressMonitor) 基於用戶選擇的對象做一個的初 始檢查,這個通常由界面自動執行。返回RefactoringStatus.FATAL表明初始檢 查沒有通過,重構操作不能繼續。
4. 獲得進行重構的其他參數,比如,對重命名操作來說就是指新名字。這個 通常是由界面根據用戶的輸入提供的。由具體的實現類給出相應的方法。
5. 獲得用戶輸入參數後,調用Refactoring的checkFinalConditions (IProgressMonitor)進行剩下的檢查,這個通常由界面自動執行,返回 RefactoringStatus.FATAL表明最後的檢查沒有通過,重構操作不能繼續。
6. 調用Refactoring的createChange(IProgressMonitor)獲得Change對象, 這個通常由界面自動執行,界面可以根據Change對象顯示預覽界面。
基於以上的介紹,為了實現本文工程中的重構操作,我們需要擴展 Refactoring類,為它增加一個構造函數,並且具體實現 checkInitialConditions、checkFinalConditions和createChange三個函數。
首先通過菜單File -> New->Class彈出創建類的對話框,輸入包名 manage.annotation.refactor,類名 AnnotationRefactoring,輸入父類 org.eclipse.ltk.core.refactoring.Refactoring,選中"繼承抽象方法"復選框 ,點擊完成按鈕,一個擴展了Refactoring的最基本的類AnnotationRefactoring 就被創建出來了。
首先為其增加構造函數,以用戶選擇的Java模型元素作為參數。Refactoring 分析這個參數以得到所有相關的可寫Java文件,作為重構操作的對象,如果該模 型元素包含在Java文件中,則找到包含它的文件節點;如果該模型元素包含Java 文件,則找到它的所有子Java文件。構造函數代碼如下:
清單 6
public AnnotationRefactoring(IJavaElement element) {
while (element.getElementType() > IJavaElement.COMPILATION_UNIT) {
element = element.getParent();
if (element == null)
return;
}
if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
if (!element.isReadOnly())
compilationUnits.add(element);
}
if (element.getElementType() < IJavaElement.COMPILATION_UNIT)
findWritableCompilationUnits(element);
}
接著完成checkInitialConditions函數,實現初始檢查的具體操作。作為示 例,在本文工程中我們不進行任何具體的檢查操作,只簡單得給出初始檢查成功 的信息,返回RefactoringStatus.
INFO以使重構操作繼續執行。checkInitialConditions函數代碼如下:
清單 7
public RefactoringStatus checkInitialConditions (IProgressMonitor pm)
throws CoreException, OperationCanceledException {
return RefactoringStatus.createInfoStatus("Initial Condition is OK!");
}
接著完成checkFinalConditions函數,實現獲得用戶輸入參數後的後續檢查 操作。在本文工程中,我們首先收集所有需要添加注釋的以test 開頭的方法, 判斷是否不存在這樣的方法,如果不存在給出出錯信息,返回 RefactoringStatus.FATAL以結束重構操作;如果存在這樣的方法,則給出後續 檢查成功的信息,返回RefactoringStatus.
INFO。checkFinalConditions函數代碼如下:
清單 8
public RefactoringStatus checkFinalConditions (IProgressMonitor pm)
throws CoreException, OperationCanceledException {
collectChanges();
if (fChangeManager.size() == 0)
return RefactoringStatus
.createFatalErrorStatus("No testing methods found!");
else return RefactoringStatus.createInfoStatus("Final condition is OK!");
}
最後,創建Change對象的createChange函數是整個重構操作中最核心的代碼 ,它的實現將在下面章節中介紹。
6 使用AST構造Change對象
當我們找到了修改的位置時,我們有兩個選擇:
1. 通過IScanner接口掃描代碼,然後通過IBuffer接口直接修改代碼
2. 通過遍歷和編輯AST樹進行結構化的修改
DeveloperWorks提供的文章《擴展Eclipse的Java開發工具》中,給出了使用 IBuffer接口的例子。現在我們要講述使用AST來遍歷和修改Java源代碼的方法。
AST是abstract syntax tree的縮寫。它是Eclipse中的Java開發環境(JDT)為 我們提供的極為強大的源代碼解析和編輯工具。
在使用AST樹提供的功能之前,我們首先要創建AST樹。由於AST樹的構建是一 項費時的操作,JDT缺省情況下不將源代碼解析為AST樹。下面的代碼演示了獲得 一個ICompilationUnit對應的AST樹的過程。在JDT提供的API中, ICompilationUnit接口用於表示一個可以被編譯的源代碼文件。在我們提供的例 子程序中,我們通過下面的代碼將整個文件解析成為了一顆AST樹。
清單 9
ASTParser parser = ASTParser.newParser (AST.JLS3);
parser.setSource(cu);
ASTNode root = parser.createAST(null);
AST樹中的每個節點都是ASTNode類型,通過Visit模式,我們可以訪問一個 ASTNode包含的所有節點。下面的代碼演示了訪問一個AST節點並獲得其中所有的 MethodDeclaration節點的方法。
清單 10
private void getMethods(ASTNode cuu, final List methods) {
cuu.accept(new ASTVisitor() {
public boolean visit(MethodDeclaration node) {
methods.add(node);
return false;
}
});
}
在收集到了所有的MethodDeclaration節點之後,我們就可以通過向AST樹中 插入和刪除節點或者修改已有的節點的方法來修改AST樹了。下面的代碼演示了 使用AST工具為方法添加@Test Annotation的功能。
清單 11
private boolean collectChanges(CompilationUnit root,
MethodDeclaration method) {
if (method.getName().getFullyQualifiedName().startsWith("test")) {
AST ast = method.getAST();
if (needTimeout) {
NormalAnnotation na = ast.newNormalAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
MemberValuePair pair = ast.newMemberValuePair();
pair.setName(ast.newSimpleName("timeout"));
pair.setValue(ast.newNumberLiteral("500"));
na.values().add(pair);
method.modifiers().add(0, na);
} else {
MarkerAnnotation na = ast.newMarkerAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
method.modifiers().add(0, na);
}
return true;
}
return false;
}
在 Refactoring框架中,我們要求對AST樹的修改並不立刻反映到源代碼中。 相反,我們需要一個能記錄整個修改過程的Change對象。Refactoring框架將利 用這個Change對象來顯示Priveiw窗口、進行Undo和Redo等操作。大致上,我們 記錄對一個AST樹的修改從而生成Change對象的過程如以下代碼所示。
清單 12
root.recordModifications();
//在這裡修改AST樹…
TextEdit edits = root.rewrite(document, cu.getJavaProject()
.getOptions(true));
TextFileChange change = new TextFileChange("", (IFile) cu
.getResource());
change.setEdit(edits);
最後,由於Refactoring類的createChange方法僅返回一個Change對象,如果 我們需要對多個源代碼文件進行修改,我們可以利用 CompositeChange類將多個 Change對象封裝成一個Change對象。這個過程可能類似如下代碼所執行的流程
清單 13
public Change createChange(IProgressMonitor pm) throws CoreException,
OperationCanceledException {
Change[] changes = new Change[fChangeManager.size()];
System.arraycopy(fChangeManager.toArray(), 0, changes, 0,
fChangeManager.size());
CompositeChange change = new CompositeChange(
"Add @Override Annotation", changes);
return change;
}
7 擴展RefactoringWizard 框架
Eclipse中的RefactoringWizard框架擴展了Eclipse的Wizard框架,關於 Wizard框架的介紹可以在Eclipse的幫助系統中找到,這裡我們僅從OO設計和架 構的角度探討一下RefactoringWizard框架。
我們從Wizard相關的幾個類開始:
1. WizardPage類
WizardPage是一個包含了多個界面元素(比如文本框Text,按鈕Button)的 一個界面組合部分。各個Page之間是獨立的,是可以動態加載的。WizardPage類 的職責有:
組合SWT界面元素,構造出一個界面頁。
定義本身界面元素的操作行為。
在 RefactoringWizard框架中預設了兩個通用的屬性頁:PreviewWizardPage 和ErrorWizardPage。PreviewWizardPage類是用來預覽重構後的修改,對比代碼 或其他資源的變化。ErrorWizardPage類是用來處理條件檢查及錯誤狀態通知的 。我們只需擴展RefactoringWizard框架就可以自動獲取這兩項強大的功能。
2. Wizard類
一個Wizard就是一個裝載一系列WizardPage頁的容器,Wizard類的職責有:
裝載一系列WizardPage,構造出一個復雜的界面。
裝載領域類來處理具體業務邏輯。(在RefactoringWizard框架中這個類就是 Refactoring類)
維護WizardPage頁之間以及頁與領域類之間的數據傳遞和狀態共享。(在這 裡要補充一點,其實在具體RefactoringWizard框架的實現中有專門的類來分擔 這部分職責。)
我們的界面行為可以千變萬化(通過組合不同的WizardPage),而負責處理業 務邏輯的領域類也可以獨立的變化,你可以隨意擴展Wizard的界面功能 (-對擴 展開放),而不用修改現有RefactoringWizard框架(-對修改封閉),這正是OO設 計的最基本原則-OCP(Open-Close Principle)。
3. WizardDialog類
這個對話框類的主要職責是構造一個完整的GUI界面以及操作界面。它預設了 一些按鈕(Back,Next,Finish,Cancel)等界面元素,它負責裝載Wizard類,操 作時通過按鈕Back、Next來在多個WizardPage之間切換。
下面我們給出RefactoringWizard框架的架構圖:
圖 5 Refactoring Wizard架構圖
從圖 5中我們可以看到,如果我們把每一個WizardPage頁看作一項業務,那 麼Refactoring正是處理業務邏輯的控制中心,它封裝了所有對業務邏輯的處理 ,當然它可以在將處理任務委任出去。但請注意,它並不負責實現業務流程,也 就是說各業務(各個Page界面)之間的邏輯順序關系不由它維護。
RefactoringWizard 框架充分考慮到了應用的可擴展性,它在SWT的MVC(模型 -視圖-控制)元架構模式的基礎上,添加了一些新的架構元素。MVC模式促使業 務邏輯與界面分離,界面與控制行為分離,而RefactoringWizard框架增強了界 面本身分離的特性,它將一個完整的界面分拆成多個頁面,用戶可以動態組合這 些頁面或添加新的頁面來擴展界面行為。這種特性-界面的動態組合,低耦合, 高內聚,封裝良好的接口-讓我們領略到了OO設計的精髓。
下面我們通過以下幾個步驟來擴展RefactoringWizard框架:
擴展RefactoringWizardPage
擴展RefactoringWizard
啟動RefactoringWizard
第一步,擴展RefactoringWizardPage:首先我們新建一個類 AnnotationRefactoringWizardPage,它需要繼承UserInputWizardPage類(其父 類是RefactoringWizardPage,而RefactoringWizardPage最終實現了 IDialogPage接口)。接下來就是實現IDialogPage接口的createControl(…)方法 ,在這個方法裡實現你的界面行為,比如我們例子中的TimeOut文本框,代碼清 單如下:
清單 14
/**
* create composite to add UI elements
*/
public void createControl(Composite parent) {
// define UI
Composite composite = new Composite(parent, SWT.NONE);
GridLayout lay = new GridLayout();
lay.numColumns = 2;
composite.setLayout(lay);
btnCheck = new Button(composite, SWT.CHECK);
btnCheck.setText("Add timeout parameter");
GridData gdBtnCheck = new GridData();
gdBtnCheck.horizontalSpan = 2;
gdBtnCheck.horizontalAlignment = GridData.FILL;
btnCheck.setLayoutData(gdBtnCheck);
labName = new Label(composite, SWT.WRAP);
labName.setText("TimeOut:");
GridData gdLabName = new GridData();
gdLabName.horizontalAlignment = GridData.BEGINNING;
gdLabName.grabExcessHorizontalSpace = true;
labName.setLayoutData(gdLabName);
txtTimeOut = new Text(composite, SWT.SINGLE | SWT.BORDER);
GridData gdTxtTimeOut = new GridData();
gdTxtTimeOut.horizontalAlignment = GridData.END;
gdLabName.grabExcessHorizontalSpace = true;
txtTimeOut.setLayoutData(gdTxtTimeOut);
txtTimeOut.setText("500");
// init status
labName.setEnabled(false);
txtTimeOut.setEnabled(false);
// add listener
defineListener();
// 將composite納入框架的控制
setControl(composite);
Dialog.applyDialogFont(composite);
}
在這裡我們要特別注意的一點是在定義完我們的界面元素後,需要將自定義 的Composite納入框架的控制,就是這行代碼:"setControl(composite);"
在我們處理完輸入數據檢查後進入下一頁面之前,我們需要設置頁面完成的 狀態,以及傳遞輸入數據到領域類Refactoring。我們用以下代碼設置好頁面完 成的狀態後,下個頁面ErrorWizardPage就會處理顯示邏輯:
清單 15
private void notifyStatus(boolean valid, String message) {
//設置錯誤信息
setErrorMessage(message);
//設置頁面完成狀態
setPageComplete(valid);
}
傳遞輸入數據通過以下代碼來處理:
清單 16
private void setRefactoring(boolean selection, String text) {
AnnotationRefactoring refactoring = (AnnotationRefactoring) getRefactoring();
refactoring.setNeedTimeout(true);
if(selection) {
refactoring.setTimeout(Integer.valueOf(txtTimeOut.getText ()).intValue());
}
}
其中getRefactoring()方法是繼承自RefactoringWizardPage的方法,由於我 們的RefactoringWizard類裝載了RefactoringWizardPage和Refactoring類,這 個方法是從RefactoringWizard類中獲得的,這裡面用到了 Observer設計模式。 至此,我們完成RefactoringWizardPage的擴展。
第二步,擴展 RefactoringWizard:首先我們新建一個類 AnnotationRefactoringWizard,它需要繼承 RefactoringWizard類,這個類中 我們只需要加載定義好的AnnotationRefactoringWizardPage類和 AnnotationRefactoring類,當然復雜的處理已經有RefactoringWizard框架處理 好了。下面我們在構造函數中加載 Refactoring類:
清單 17
public AnnotationRefactoringWizard(Refactoring refactoring) {
super(refactoring, WIZARD_BASED_USER_INTERFACE);
}
然後我們加載我們的AnnotationRefactoringWizardPage類,只需重載父類 RefactoringWizard的addUserInputPages()方法就可以:
清單 18
protected void addUserInputPages() {
page = new AnnotationRefactoringWizardPage("refactor annotation");
addPage(page);
}
第三步,啟動RefactoringWizard。擴展好RefactoringWizard之後,就需要 在用戶點擊菜單項或是按鈕時彈出這個對話框。RefactoringWizard最好使用 RefactoringWizardOpenOperation類來打開(當然也可以用 RefactoringWizardDialog)。RefactoringWizardOpenOperation首先進行重構的 初始檢查,通過後才打開RefactoringWinzard對話框,否則就會打開錯誤對話框 。前面完成創建插件工程時我們提到,彈出RefactoringWizard對話框的代碼應 該放到響應菜單操作的類的run函數中。具體到本文工程中,就是把下面的代碼 放到AnnotationManageAction的run函數中。這些代碼首先依次構造Refactoring 和RefacoringWizard子類AnnotationRefactoring和 AnnotationRefactoringWizard,並將AnnotationRefactoring的引用傳遞給 AnnotationRefactoringWizard,然後用RefactoringWizardOpenOperation打開 AnnotationRefactoringWizard,彈出向導對話框。
清單 19
public void run(IAction action) {
Shell shell = window.getShell();
AnnotationRefactoring refactor = new AnnotationRefactoring (select);
AnnotationRefactoringWizard wizard = new AnnotationRefactoringWizard(
refactor);
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(
wizard);
try {
op.run(shell, "Inserting @Override Annotation");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
8 小結
在 Eclipse中有效的利用重構能夠大大的減輕軟件開發人員的工作負擔,提 高軟件的健壯性。然而,目前重構仍然處在一個工具缺乏的時代。以Eclipse 為 例,只有JDT提供的重構工具最為完善,而針對其他語言例如C++、Python等的開 發環境,都缺乏對應的重構功能。
通過本文提供的方法,我們能夠有效的利用Eclipse中的重構框架創建新的重 構,從而進一步提高已有開發環境的效率。