【工利其器】必会工具之(四)Refactor篇——Android Studio在代码重构中的妙用
转载请声明,转载自【https://www.cnblogs.com/andy-songwei/p/11809700.html】,谢谢!
代码重构几乎是每个程序员在软件开发中必须要不断去做的事情,以此来不断提高代码的质量。Android Stido(以下简称AS)以其强大的功能,成为当下Android开发工程师最受欢迎的开发工具,也是Android官方推荐使用的工具。如此优秀的工具,自然少不了要在代码重构这件事情上好好表现一把了。本文将通过代码演示,功能截图来详细介绍AS为代码重构提供的各项功能。
在AS的主菜单栏中有一项“Refactor”下拉菜单,点击该下拉菜单,会看到如下的界面,菜单中的每一项,都是为代码重构提供的一项自动实现功能。这么多的功能项,可见AS在代码重构功能上的强大,下面我们对这些功能项一一进行介绍。另外,还可以在编辑界面中点击右键,在弹出的菜单中也可以找到“Refactor”。
1、Refactor This
作用:重构当前。操作此项,会显示对当前光标选中处可行的重构方法。
示例:选择了类名“RefactorTest”,操作“Refactor This”后,显示了可执行的重构方法列表,可以通过选择数字来执行对应的方法。
2、Rename
作用:对光标选中项进行重命名。不仅可以对类中的成员变量进行重命名,还能对文件名,包名等进行重命名,Module中与之相关联的所有地方都会一起修改,而不用一一手动修改。
快捷键:Shift + F6
示例:在红框中输入修改后的名称,并按Enter键即可。
3、Rename File
作用:修改当前编辑界面显示的文件的文件名。就相当于鼠标选中该文件,并执行“Rename”方法。
示例:在显示的对话框中输入新文件名。可以在下方的选项框中选择修改范围,引用该文件的地方,注释,字符串中都可以选择一起修改。
4、Change Signature
作用:修改方法、类、构造函数的签名,其实就是修改所选项的一些属性。
快捷键:Ctr l+ F6
示例:如下展示了一个方法重构前,重构过程,以及重构后的情形(以修改一个方法签名为例)。
重构前:
1 private void testChangeSignature(int first, int second) { 2 }
选中方法名后,执行该重构方法后,会弹出如下对话框,可以对该方法各种属性进行修改,添加/删除参数,调整参数顺序,新增/删除异常等。
重构后:
1 public void testChangeSignature(int second, int first, String third) throws NullPointerException { 2 }
5、Type Migration
作用:类型迁移,即对变量数据类型,或者方法的返回类型进行修改。前面介绍了对文件名,包名,变量名等进行修改,这里对类型进行修改。
快捷键:Ctrl + Shift + F6
重构前:
1 private int age = 10; 2 public RefactorTest(int age) { 3 this.age = age; 4 }
选中要修改的类型,执行该重构方法,会弹出对话框,根据需要编辑类型,选中作用范围即可。指定范围内,与该变量相关联处都会被修改。
重构后(由于从int修改到String,所以还需要手动修改变量值):
1 private String age = "10"; 2 public RefactorTest(String age) { 3 this.age = age; 4 }
6、Make Static
作用:给内部类或者方法添加static关键字。示例比较简单,就不做演示了。
7、Convert To Instance Method
作用: 转换为实例方法,即将静态方法去掉static关键字。
8、Move
功能:移动文件到指定路径
快捷键:F6
9、Copy
作用:在指定包中拷贝一份当前文件
快捷键:F5
10、Safe Detele
作用:安全删除,可用于对方法/字段等进行快速删除,会删除掉与之相关联的引用。
快捷键:Alt + Delete
11、Extract
(1)Variable
作用:提取变量。这一点在碰到比较长的表达式时经常用到,将看起来很长很复杂的表达式提取出来作为一个变量表示。
快捷键:Ctrl + Alt + V
重构前:我们常会看到这样的代码
1 public void testExtractVariable() { 2 Log.i("demo", "age=" + getAaaaaaaaaaaaaaaaaaaaaaaaaaaAge() + ";name=" + getNnnnnnnnnnnnnnnnnnnnnnname()); 3 } 4 private int getAaaaaaaaaaaaaaaaaaaaaaaaaaaAge() { 5 return age; 6 } 7 private String getNnnnnnnnnnnnnnnnnnnnnnname() { 8 return name; 9 }
第二行的要打印的信息表达式太长了,希望单独提取出来用一个变量表示。本示例中鼠标停留在第2行“getAaaaaaaaaaaaaaaaaaaaaaaaaaaAge”处,执行该重构方法,会弹出如下红框部分对话框,显示的是选中表达式相关的可提取部分,根据需要选择要提取的部分即可。
重构后:
1 public void testExtractVariable() { 2 String msg = "age=" + getAaaaaaaaaaaaaaaaaaaaaaaaaaaAge() + ";name=" + getNnnnnnnnnnnnnnnnnnnnnnname(); 3 Log.i("demo", msg); 4 } 5 private int getAaaaaaaaaaaaaaaaaaaaaaaaaaaAge() { 6 return age; 7 } 8 private String getNnnnnnnnnnnnnnnnnnnnnnname() { 9 return name; 10 }
(2)Constant
作用:提取常量,将表达式中的值提取为常量。
快捷键:Ctrl + Alt +C
重构前:
1 public void testExtractConstant() { 2 String filename = "sdcard"; 3 }
重构后:
1 public static final String SDCARD = "sdcard"; 2 public void testExtractConstant() { 3 String filename = SDCARD; 4 }
(3)Filed
作用:提取字段,将局部变量提取为全局变量。
快捷键:Ctrl + Alt +F
重构前:
1 public void testExtractField() { 2 String name ="zhangsan"; 3 }
重构后:
1 private final String string = "zhangsan"; 2 public void testExtractField() { 3 }
(4)Parameter
作用:将局部变量提取为方法的参数。
快捷键:Ctrl + Alt +P
重构前:
1 public void testExtractParameter() { 2 printName(); 3 } 4 private void printName(){ 5 String name = "zhangsan"; 6 Log.i("demo","My name is:"+name); 7 }
重构后:
1 public void testExtractParameter() { 2 printName("zhangsan"); 3 } 4 private void printName(String name){ 5 Log.i("demo","My name is:"+ name); 6 }
(5)Functional Parameter ( 函数式参数 ) Ctrl + Alt + Shift + P
(6)Parameter Object
作用:将参数提取为一个对象。该功能主要是针对参数比较多的时候,将这些参数提取出来作为一个Bean实例传入。
重构前:
1 private void testExtractParamObject() { 2 String info = getInfo("zhangshan", 20, 180f); 3 } 4 private String getInfo(String name, int age, float height) { 5 return "name=" + name + ";age=" + age + ";height=" + height; 6 }
重构后:
1 private void testExtractParamObject() { 2 String info = getInfo(new Person("zhangshan", 20, 180f)); 3 } 4 private String getInfo(Person person) { 5 return "name=" + person.getName() + ";age=" + person.getAge() + ";height=" + person.getHeight(); 6 } 7 private static class Person { 8 private final String name; 9 private final int age; 10 private final float height; 11 private Person(String name, int age, float height) { 12 this.name = name; 13 this.age = age; 14 this.height = height; 15 } 16 public String getName() { 17 return name; 18 } 19 public int getAge() { 20 return age; 21 } 22 public float getHeight() { 23 return height; 24 } 25 }
(7)Mehtod
作用:提取为方法
快捷键:Ctrl + Alt +M
重构前:
1 public void testExtractMethod() { 2 List<String> nameList = new ArrayList<>(); 3 nameList.add("zhangshan"); 4 nameList.add("lisi"); 5 nameList.add("wangwu"); 6 int size = nameList.size(); 7 }
鼠标光标选中第2~5行后执行该重构方法
重构后:
1 public void testExtractMethod() { 2 List<String> nameList = getNameList(); 3 int size = nameList.size(); 4 } 5 @NonNull 6 private List<String> getNameList() { 7 List<String> nameList = new ArrayList<>(); 8 nameList.add("zhangshan"); 9 nameList.add("lisi"); 10 nameList.add("wangwu"); 11 return nameList; 12 }
(8)Type Parameter
(9)Method Object
作用:将该选中的内容提取为一个方法,并提取到一个独立的类中。和“Method”很类似,不同的是提取的方法最后放在哪里。
重构前:
1 public void testExtractMethod() { 2 List<String> nameList = new ArrayList<>(); 3 nameList.add("zhangshan"); 4 nameList.add("lisi"); 5 nameList.add("wangwu"); 6 int size = nameList.size(); 7 }
重构后:
1 public void testExtractMethod() { 2 List<String> nameList = Utils.invoke(); 3 int size = nameList.size(); 4 } 5 private static class Utils { 6 private static List<String> invoke() { 7 List<String> nameList = new ArrayList<>(); 8 nameList.add("zhangshan"); 9 nameList.add("lisi"); 10 nameList.add("wangwu"); 11 return nameList; 12 } 13 }
(10)Delegate
作用:提取为一个代理类。
重构前:
1 public class RefactorTest{ 2 3 public void testExtractInterface() { 4 System.out.print("testExtractInterface"); 5 } 6 }
重构后:
1 public class RefactorTestDelegate { 2 public RefactorTestDelegate() { 3 } 4 5 public void testExtractInterface() { 6 System.out.print("testExtractInterface"); 7 } 8 } 9 10 public class RefactorTest{ 11 12 private final RefactorTestDelegate refactorTestDelegate = new RefactorTestDelegate(); 13 14 public void testExtractInterface() { 15 refactorTestDelegate.testExtractInterface(); 16 } 17 }
(11)Interrface
作用:提取为接口。
重构前:
1 public class RefactorTest { 2 3 public void testExtractInterface() { 4 System.out.print("testExtractInterface"); 5 } 6 }
public修饰的方法才可以被提取到接口中。
重构后:
1 interface IRefactorTest { 2 void testExtractInterface(); 3 } 4 5 public class RefactorTest implements IRefactorTest { 6 7 @Override 8 public void testExtractInterface() { 9 System.out.print("testExtractInterface"); 10 } 11 }
(12)Superclass
作用:将指定内容提取到父类中。
重构前:
1 private void testExtractSupperclass() { 2 testSuper(); 3 } 4 5 public void testSuper() { 6 System.out.print("testSuper"); 7 }
重构后:
1 //=======RefactorTest extends RefactorTestBase======= 2 private void testExtractSupperclass() { 3 testSuper(); 4 } 5 6 class RefactorTestBase { 7 public void testSuper() { 8 System.out.print("testSuper"); 9 } 10 }
(13) Style
作用:将属性提取为Style。该项只有当鼠标停留在布局文件中时才会出现。
重构前:
1 <Button 2 android:id="@+id/btn_handler_demo" 3 android:layout_width="wrap_content" 4 android:layout_height="wrap_content" 5 android:text="handler" />
重构后:
1 <Button 2 android:id="@+id/btn_handler_demo" 3 android:text="handler" 4 style="@style/testStyle" />
styles.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <style name="testStyle"> 4 <item name="android:layout_width">wrap_content</item> 5 <item name="android:layout_height">wrap_content</item> 6 </style> 7 </resources>
(14)Layout
作用:提取为布局文件。这一项也是需要在鼠标停留在布局文件中时才会出现。
1 <LinearLayout 2 android:layout_width="match_parent" 3 android:layout_height="wrap_content" 4 android:orientation="horizontal"> 5 <Button 6 android:id="@+id/btn_handler_demo" 7 android:layout_width="wrap_content" 8 android:layout_height="wrap_content" 9 android:text="handler" /> 10 <Button 11 android:id="@+id/btn_broadcast_demo" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="Broadcast" /> 15 <Button 16 android:id="@+id/btn_bright_demo" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:text="Bright" /> 20 <Button 21 android:id="@+id/btn_service_demo" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:text="Service" /> 25 </LinearLayout>
重构后:
<include layout="@layout/testlayout" />
testlayout.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="wrap_content" 6 android:orientation="horizontal" 7 tools:showIn="@layout/activity_main"> 8 9 <Button 10 android:id="@+id/btn_preference_demo" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:text="Preference" /> 14 15 <Button 16 android:id="@+id/btn_file_demo" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:text="File" /> 20 21 <Button 22 android:id="@+id/btn_anim_demo" 23 android:layout_width="wrap_content" 24 android:layout_height="wrap_content" 25 android:text="Anim" /> 26 27 <Button 28 android:id="@+id/btn_customview_demo" 29 android:layout_width="wrap_content" 30 android:layout_height="wrap_content" 31 android:text="CustomView" /> 32 </LinearLayout>
12、Inline
作用:转换为内联、方法链形式的调用。
快捷键:Ctrl + Alt +N
重构前:
1 private void testInline() { 2 int a = 100; 3 int b = 200; 4 System.out.print(add(a, b)); 5 } 6 private int add(int a, int b) { 7 System.out.print("a=" + a + ";b=" + b); 8 return a*2 + b*3; 9 }
重构后:
1 private void testInline() { 2 int a = 100; 3 int b = 200; 4 System.out.print("a=" + a + ";b=" + b); 5 System.out.print(a * 2 + b * 3); 6 }
原先需要调用一个方法,重构后直接把该方法中的代码给复制过来了。因为上面选中的是内联所有的,并且删除该方法,所以add方法也就被删除了。
13、Find and Replace Code Duplicates
14、Invert Boolean
作用:转换Boolean值,将当前false/true的值进行转化为相反的值。
重构前:
1 private boolean isEmpty(String str) { 2 if (str != null && str.length() == 0) { 3 return false; 4 } 5 return true; 6 }
重构后:
1 private boolean isNotEmpty(String str) { 2 if (str != null && str.length() == 0) { 3 return true; 4 } 5 return false; 6 }
15、Pull Members Up
作用:将子类的成员上移到父类中。
重构前:
1 public class RefactorBase { 2 3 } 4 5 public class RafactorSub extends RefactorBase { 6 int age = 10; 7 8 public void printSub() { 9 System.out.print(age); 10 } 11 }
重构后:
1 public class RefactorBase { 2 int age = 10; 3 public void printSub() { 4 System.out.print(age); 5 } 6 } 7 8 public class RafactorSub extends RefactorBase { 9 10 }
16、Push Members Down
作用:将父类中的成员下移到子类中,正好是“Pull Members Up”的反向操作。
重构前:
1 public class RefactorBase { 2 int age = 10; 3 public void printSub() { 4 System.out.print(age); 5 } 6 } 7 8 public class RafactorSub extends RefactorBase { 9 10 }
重构后:
1 public class RefactorBase { 2 3 } 4 public class RafactorSub extends RefactorBase { 5 int age = 10; 6 public void printSub() { 7 System.out.print(age); 8 } 9 }
17、Use Interface Where Possible
18、Replace Inheritance with Delegation
作用:使用代理替代继承。在java中,提倡使用组合,而不是继承。
重构前:
1 public abstract class AbsClass { 2 public abstract void print(); 3 } 4 5 public class ClassWrapper extends AbsClass { 6 @Override 7 public void print() { 8 System.out.print("print"); 9 } 10 } 11 12 private void testReplaceInheritanceWithDelegation() { 13 new ClassWrapper().print(); 14 }
重构后:
1 public abstract class AbsClass { 2 public abstract void print(); 3 } 4 5 public class ClassWrapper { 6 private final ClassImpl absClass = new ClassImpl(); 7 8 public void print() { 9 absClass.print(); 10 } 11 12 private class ClassImpl extends AbsClass { 13 @Override 14 public void print() { 15 System.out.print("print"); 16 } 17 } 18 } 19 20 public class RefactorTest { 21 private void testReplaceInheritanceWithDelegation() { 22 new ClassWrapper().print(); 23 } 24 }
这一部分有点像Android中Context,ContextWrapper,ContextImpl类之间的关系。
19、Remove Middleman
作用:移除中间人,其实就是移除中间过程。
重构前:
1 public class RefactorTest { 2 3 private void testRemoveMiddleMan() { 4 BookManager bookManager = new BookManager(); 5 bookManager.addBook("java"); 6 } 7 8 public static class BookManager { 9 private List<String> mBookList = new ArrayList<>(); 10 11 private void addBook(String bookName) { 12 mBookList.add(bookName); 13 } 14 } 15 }
重构后:
1 public class RefactorTest { 2 3 private void testRemoveMiddleMan() { 4 BookManager bookManager = new BookManager(); 5 bookManager.getmBookList().add("java"); 6 } 7 8 public static class BookManager { 9 private List<String> mBookList = new ArrayList<>(); 10 11 public List<String> getmBookList() { 12 return mBookList; 13 } 14 } 15 }
对比重构前和重构后会发现,添加book这个动作,从由BookManager的addBook方法来执行,变成了直接有mBookList来执行了。这个addBook就是这个MiddleMan,显得多余,可以优化掉。实际上优化后就变成一个inline方式了,可以对比前面讲到的“Inline”。
20、Wrap Method Return Value
作用:封装返回值
1 public class RefactorTest { 2 3 private void testWrapReturnValue() { 4 String name = getName(); 5 } 6 7 private String getName() { 8 return "zhangsan"; 9 } 10 }
重构后:
1 public class RefactorTest { 2 3 private void testWrapReturnValue() { 4 String name = getName().getValue(); 5 } 6 7 private Person getName() { 8 return new Person("zhangsan"); 9 } 10 11 public class Person { 12 private final String value; 13 14 public Person(String value) { 15 this.value = value; 16 } 17 18 public String getValue() { 19 return value; 20 } 21 } 22 }
21、Convert Anonymous to Inner
作用:将匿名内部类转为内部类。
重构前:
1 private void testConvertAnonymousToInner(){ 2 view.setOnClickListener(new View.OnClickListener() { 3 @Override 4 public void onClick(View v) { 5 } 6 }); 7 }
重构后:
1 public class RefactorTest{ 2 3 View view; 4 private void testConvertAnonymousToInner(){ 5 view.setOnClickListener(new MyOnClickListener()); 6 } 7 8 private static class MyOnClickListener implements View.OnClickListener { 9 @Override 10 public void onClick(View v) { 11 12 } 13 } 14 }
22、Encapsulate Fields
作用:封装字段,用于生成Getter/Setter
重构前:
1 public String name = "zhangsan"; 2 private void testEncapsulateFields() { 3 System.out.println(name); 4 }
通过该对话框,可以选择要封装的字段,设置修饰符等。默认选择时,name字段的修饰符从public变成了private,这也就避免了外部类通过实例直接访问它。
重构后:
1 private String name = "zhangsan"; 2 private void testEncapsulateFields() { 3 System.out.println(getName()); 4 } 5 public String getName() { 6 return name; 7 } 8 public void setName(String name) { 9 this.name = name; 10 }
23、Replace Temp With Query
24、Replace Constructor with Factory Method
作用:将构造方法替换为工厂方法
重构前:
1 public class MyClass { 2 3 private String title; 4 private String message; 5 private String sure; 6 private String cancel; 7 8 public MyClass(String title, String message, String sure, String cancel) { 9 this.title = title; 10 this.message = message; 11 this.sure = sure; 12 this.cancel = cancel; 13 } 14 } 15 16 public class RefactorTest { 17 private void testReplaceConstructorWithFactory(Context context) { 18 MyClass myClass = new MyClass("title", "message", "sure", "cancle"); 19 } 20 }
重构后:
1 public class MyClass { 2 3 private String title; 4 private String message; 5 private String sure; 6 private String cancel; 7 8 private MyClass(String title, String message, String sure, String cancel) { 9 this.title = title; 10 this.message = message; 11 this.sure = sure; 12 this.cancel = cancel; 13 } 14 15 public static MyClass createMyClass(String title, String message, String sure, String cancel) { 16 return new MyClass(title, message, sure, cancel); 17 } 18 } 19 20 public class RefactorTest { 21 private void testReplaceConstructorWithFactory(Context context) { 22 MyClass myClass = MyClass.createMyClass("title", "message", "sure", "cancle"); 23 } 24 }
原先public修饰的构造函数,已经变成private了,MyClass类只能通过工厂方法来获取实例,而无法再直接new了。
25、Replace Constructor with Builder
作用:将构造方法替换为Builder方式
重构前:
1 public class RefactorTest{ 2 private void testReplaceConstructorWithBuilder(Context context){ 3 MyDialog dialog = new MyDialog(context,"title","message","sure","cancle"); 4 } 5 } 6 7 public class MyDialog extends Dialog { 8 private String title; 9 private String message; 10 private String sure; 11 private String cancel; 12 public MyDialog(@NonNull Context context) { 13 super(context); 14 } 15 public MyDialog(Context context, String title, String message, String sure, String cancel) { 16 super(context); 17 this.title = title; 18 this.message = message; 19 this.sure = sure; 20 this.cancel = cancel; 21 } 22 }
重构后:
1 public class RefactorTest { 2 private void testReplaceConstructorWithBuilder(Context context) { 3 MyDialog dialog = new MyDialogBuilder() 4 .setContext(context) 5 .setTitle("title") 6 .setMessage("message") 7 .setSure("sure") 8 .setCancel("cancle") 9 .createMyDialog(); 10 } 11 } 12 13 public class MyDialogBuilder { 14 private Context context; 15 private String title; 16 private String message; 17 private String sure; 18 private String cancel; 19 20 public MyDialogBuilder setContext(Context context) { 21 this.context = context; 22 return this; 23 } 24 25 public MyDialogBuilder setTitle(String title) { 26 this.title = title; 27 return this; 28 } 29 30 public MyDialogBuilder setMessage(String message) { 31 this.message = message; 32 return this; 33 } 34 35 public MyDialogBuilder setSure(String sure) { 36 this.sure = sure; 37 return this; 38 } 39 40 public MyDialogBuilder setCancel(String cancel) { 41 this.cancel = cancel; 42 return this; 43 } 44 45 public MyDialog createMyDialog() { 46 return new MyDialog(context); 47 } 48 }
看到这里,我们应该能够联想到AlertDialog类中的Builder了。将构造函数的形式,转变为了建造者模式的形式,这样不会拘泥于构造函数的参数个数,参数类型的限制,从而灵活设置属性。
26、Generify
作用:泛型重构,自动添加泛型的参数。
重构前:
1 private void testGenerify() { 2 List list = new ArrayList(); 3 list.add("one"); 4 list.add("two"); 5 list.add("three"); 6 }
重构后:
1 private void testGenerify() { 2 List<String> list = new ArrayList<String>(); 3 list.add("one"); 4 list.add("two"); 5 list.add("three"); 6 }
27、Migrate
28、Internationalize(国际化)
29、Remove Unused Resources
作用:一直不用的资源
示例:下图中1.jpg是一个没有被应用的文件。
在执行该重构方法后,1.jpg就被删除了。