建立 對重構的強大支援是軟體開發人員喜愛Eclipse的一個最為重要的原因。而Eclipse還有一個至少和重構不相上下的優點,那就是其近乎無懈可擊的可擴充性。這兩者的結合意味著我們可以根據自己的需要來建立展新的重構功能。
介紹
重構在現代軟體開發過程中扮演著重要的角色,它能夠減輕軟體開發人員的工作負擔,提高軟體開發的生產效率。為了闡明重構的重要性,我們在這裡引用了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屬性。
結果預覽
為了給讀者一個直觀的感受,我們下面首先介紹本文中例子的實際運行效果。在閱讀完本文之後,讀者朋友也能夠順利的完成類似的功能。
啟動例子程式提供的Refactor之後,我們獲得了一個由三個頁面組成的嚮導。在第一個頁面中,使用者可以選擇是否需要timeout參數,並且使用者能夠設定timeout參數的值。
圖 1 輸入參數
當使用者輸入參數完畢之後,通過單擊Next按鈕我們將進入下一個頁面。嚮導將進行初始條件檢查和最終條件檢查,並將檢查的結果反饋給使用者。在圖 2中我們可以看到,初始條件和最終條件都正常,因此我們可以進入下一步。
圖 2 顯示條件檢查
接下來是預覽視窗(圖 3),嚮導用直觀的介面顯示了在應用嚮導之後,我們將會對原始碼造成怎樣的改動。使用者可以在這個頁面中判斷最終的修改是否符合自己的需要。另外,使用者也能夠選擇性的取消對某些檔案的修改。
當使用者檢查預覽頁面確認沒有問題之後,使用者可以按下Finish按鈕從而完成重構。這個時候,原始碼會發生修改,最後的結果如下所示:
清單 1
package main;
public class TestSomething {
@Test(timeout=500)
public void testSomething(){}
}
總體結構和流程
在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架構將修改代碼,重構作業結束。
接下來,我們將詳細介紹建立重構類型的各個步驟。
建立外掛程式工程
在大家對整個系統構架有了一個大概的瞭解之後,我們的介紹就從建立工程開始。大家都知道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對話方塊,指導使用者完成重構的工作,這些我們將在下面的章節中講述。
擴充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函數是整個重構作業中最核心的代碼,它的實現將在下面章節中介紹。
使用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;
}
擴充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();
}
}
小結
在Eclipse中有效利用重構能夠大大的減輕軟體開發人員的工作負擔,提高軟體的健壯性。然而,目前重構仍然處在一個工具缺乏的時代。以Eclipse為例,只有JDT提供的重構工具最為完善,而針對其他語言例如C++、Python等的開發環境,都缺乏對應的重構功能。 通過本文提供的方法,我們能夠有效利用Eclipse中的重構架構建立新的重構,從而進一步提高已有開發環境的效率。