項目的改造——RemoveButterKnife插件代碼的重構

前言

這篇文章記述了個人插件RemoveButterKnife的代碼改進過程以及思路,關於插件,各位能夠看RemoveButterKnife代碼庫,關於文章,能夠看構思到實現RemoveButterKnifeandroid

緣由

近期想給原來的插件RemoveButterKnife加入一些新的功能,發現之前的代碼沒有使用任何的設計模式,所有功能都寫在一塊兒,對於新功能的添加來講十分糟糕。趁此機會重構了一下代碼,在此記錄過程。git

具體步驟

插件主要分爲三個部分程序員

  1. 主插件入口部分
  2. 代碼尋找/處理部分
  3. 代碼生成部分

1. 主插件入口部分

咱們首先看第一部分,主入口部分,這部份內容主要代碼以下github

@Override
    public void actionPerformed(AnActionEvent event) {
        try {
        project = event.getData(PlatformDataKeys.PROJECT);
        Editor editor = event.getData(PlatformDataKeys.EDITOR);
        file = PsiUtilBase.getPsiFileInEditor(editor, project);
        mFactory = JavaPsiFacade.getElementFactory(project);
        mClass = getTargetClass(editor,file);
        Document document = editor.getDocument(); //以上都是從上下文中獲取的輔助對象,具體能夠查閱idea plugin文檔
        new DeleteAction(project,file,document,mClass).execute();//執行刪除操做
        }catch (Exception e){
            e.printStackTrace();
        }
    }
複製代碼

這部分主要是獲取一些須要處理的上下文變量以及下發操做給刪除操做,不須要進行處理設計模式

2. 代碼尋找/處理部分

第二部分,也是咱們的主要邏輯所在的部分,主要代碼邏輯以下 1.尋找import相關代碼,並把行號存入列表 2.尋找Api調用代碼,存入行號 3.尋找bind相關代碼,存入行號,分離id和name以及type,分別存入對應集合 4.刪除上述生成的行號集合對應代碼 5.將生成findview的指令下發給代碼生成類 經過上述邏輯,咱們能夠看到,1-3步是邏輯不相關部分,沒有先後順序,也沒有相互依賴。 那麼,咱們就能夠經過責任鏈的模式來對1-3步進行拆分。api

首先,咱們建立一個BaseChain做爲基類

BaseChain主要分爲三個部分 1.成員部分 2.處理邏輯部分 3.設置子鏈部分 代碼以下bash

public abstract class BaseChain {
   protected BaseChain next;
   protected String[] currentDoc;
   protected List<Integer> deleteLineNumbers;
   protected Map<String,String> nameAndIdMap;//第一部分,聲明成員
   public void setNext(BaseChain next){
      this.next = next;
   }//設置下一步
    final public void handle(String[] currentDoc,List deleteLineNumbers,Map nameAndIdMap){
        this.deleteLineNumbers = deleteLineNumbers;
        this.nameAndIdMap = nameAndIdMap;
        this.currentDoc = currentDoc;
        process();
        dispatcher();
    }//內部處理邏輯,沒法被子類修改
    abstract public void process();//子類須要實現的處理部分
    private void dispatcher(){
        if(next != null) {
            next.handle(currentDoc, deleteLineNumbers, nameAndIdMap);
        }
    }//轉發邏輯
}
複製代碼

而後繼續建立子Chain類

1.尋找import相關代碼,並把行號存入列表 2.尋找Api調用代碼,存入行號 3.尋找bind相關代碼,存入行號,分離id和name以及type,分別存入對應集合 咱們這裏拿尋找import相關代碼,並把行號存入列表來舉例ide

public class DetectImportChain extends BaseChain{

    public static final String IMPORT_BUTTERKNIFE_BIND = "import butterknife.Bind;";
    public static final String IMPORT_BUTTERKNIFE_INJECT_VIEW = "import butterknife.InjectView;";
    public static final String IMPORT_BUTTERKNIFE_BUTTER_KNIFE = "import butterknife.ButterKnife;";
    public static final String IMPORT_BUTTERKNIFE_BIND_VIEW = "import butterknife.BindView;";//定義了咱們須要尋找的語句

    @Override
    public void process() {
        for (int i = 0;i < currentDoc.length;i++){
            if (currentDoc[i].equals(IMPORT_BUTTERKNIFE_BIND)||currentDoc[i].equals(IMPORT_BUTTERKNIFE_BIND_VIEW)||currentDoc[i].equals(IMPORT_BUTTERKNIFE_BUTTER_KNIFE)||currentDoc[i].equals(IMPORT_BUTTERKNIFE_INJECT_VIEW)){
                deleteLineNumbers.add(i);
            }
        }
    }//進行處理
}
複製代碼

有了對應的子類,咱們還須要加上junit測試,例如工具

@Test
    public void test_with_api_use() {
        currentDoc[0] = "NotUseApi();";
        currentDoc[1] = "ButterKnife.useApi();";
        chain.handle(currentDoc,deleteLineNumbers,nameAndIdMap);
        int expect = 1;
        int result = deleteLineNumbers.size();
        assertEquals(expect,result);
    }
複製代碼

這時候咱們發現,在這幾個子類的測試中,每次都須要初始化一些集合,每一個都寫十分麻煩,因而咱們將其抽出來成爲基類,代碼以下測試

class BaseTest {
   protected Map<String,String> nameAndIdMap;
   protected Map<Integer,String> typeAndNameMap;
   protected String[] currentDoc;
   protected List<Integer> deleteLineNumbers;
   @Before
   public void init(){
       nameAndIdMap = new LinkedHashMap<>();
       typeAndNameMap = new LinkedHashMap<>();
       deleteLineNumbers = new ArrayList<>();
       currentDoc = new String[10];
   }
}
複製代碼

這樣,咱們的測試類直接繼承這個基類就能夠省下一些代碼量了。

刪除對應行代碼

此部分主要是調用idea的api進行處理,因此咱們這裏不作過多修改,把方法保留在action裏便可。

3生成findViewByid部分

生成代碼的邏輯是尋找到文本的特定位置而後依據上述找到的id,name等,進行語句的插入 這一部分前期只負責生成findViewById語句,因此作成單個工具類沒有問題。 可是隨着項目的擴展,咱們還會生成更多種類的代碼,例如onclick對應的代碼序列等,這時咱們就須要對其進行重構。

分析行爲

該部分的主要操做是尋找代碼指定部分,並使用信息生成代碼

1.拆分行爲

咱們能夠拆分爲兩個步驟 1.尋找特定部分 2.按照分類生成代碼 生成代碼部分能夠分爲基礎行爲和特定行爲,基礎行爲是指生成代碼的api調用,特定行爲是指生成的代碼根據種類不一樣而不一樣

2.拆分方案

根據上述分析,咱們可使用策略模式進行優化,每一種生成代碼都有對應的策略,咱們使用的時候只須要根據類別使用不一樣的策略類來生成便可 首先,咱們創建接口GenCodeStrategy

public interface GenCodeStrategy {
    default void genCode(PsiClass mClass, PsiElementFactory mFactory){
        genFindView(mClass,mFactory);
        genOnClick(mClass,mFactory);
    }
    void genFindView(PsiClass mClass, PsiElementFactory mFactory);//生成findviewbyid代碼
    void genOnClick(PsiClass mClass, PsiElementFactory mFactory);//生成onclick代碼
}
複製代碼

而後,讓咱們創建一個Context類,GenCodeContext

public class GenCodeContext {
    private GenCodeStrategy strategy;
    public GenCodeContext(){
    }
    public void setStrategy(GenCodeStrategy strategy){
        this.strategy = strategy;
    }
    public void executeStrategy(PsiClass mClass, PsiElementFactory mFactory){
        strategy.genCode(mClass,mFactory);
    }
}
複製代碼

再來看看咱們其中一個策略類,ActivityStrategy

public class ActivityStrategy implements GenCodeStrategy{
    private List<String> code;
    public ActivityStrategy(List<String> code){
        this.code = code;
    }
    @Override
    public void genFindView(PsiClass mClass, PsiElementFactory mFactory) {
         try {
            PsiMethod onCreate = mClass.findMethodsByName("onCreate", false)[0];
            for (PsiStatement statement : onCreate.getBody().getStatements()) {
                // Search for setContentView()
                if (statement.getFirstChild() instanceof PsiMethodCallExpression) {
                    PsiReferenceExpression methodExpression
                            = ((PsiMethodCallExpression) statement.getFirstChild())
                            .getMethodExpression();
                    if (methodExpression.getText().equals("setContentView")) {
                        for (int i = code.size() - 1; i >= 0; i--) {
                            onCreate.getBody().addAfter(mFactory.createStatementFromText(
                                    code.get(i) + "\n", mClass), statement);
                        }
                        break;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void genOnClick(PsiClass mClass, PsiElementFactory mFactory) {

    }
}
複製代碼

最後,咱們要在原來直接寫代碼生成的文件FindViewByIdWriter中使用咱們的策略模式

public class FindViewByIdWriter extends  WriteCommandAction.Simple {
    PsiClass mClass;
    private PsiElementFactory mFactory;
    List<String> code;
    Project mProject;
    public FindViewByIdWriter(Project project, PsiFile file, PsiClass psiClass, List<String> code, PsiElementFactory mFactory) {
        super(project, file);
        mClass = psiClass;
        this.code = code;
        this.mFactory = mFactory;
        mProject = project;
    }

    @Override
    protected void run(){
            GenCodeContext codeContext = new GenCodeContext();
            codeContext.setStrategy(new ActivityStrategy(code));
            codeContext.executeStrategy(mClass,mFactory);
            codeContext.setStrategy(new FragmentStrategy(code));
            codeContext.executeStrategy(mClass,mFactory);
    }
}
複製代碼

對比

咱們能夠從重構前/後的目錄結構來對比重構的效果

重構以前

重構以後

可能會有人問了,重構後感受複雜了不少,可是從邏輯的維度上來講,一個熟悉設計模式的程序員能夠很快/方便的閱讀重構後的代碼,而重構前的代碼雖然看起來文件少,可是全部邏輯都在一個文件中,每每會讓人沒法閱讀/理解

相關文章
相關標籤/搜索