[.net 面向对象编程基础] (14) 重构

[.net 面向对象编程基础] (14) 重构html

      经过面向对象三大特性:封装、继承、多态的学习,能够说咱们已经掌握了面向对象的核心。接下来的学习就是如何让咱们的代码更优雅、更高效、更易读、更易维护。固然了,这也是从一个普通程序员到一个高级程序员的必由之路。就看病同样,普通医生只能治标,高级医生不但看好病,还能除病根。程序员

1.什么时重构?编程

重构(Refactoring)就是在不改变软件现有功能的基础上,经过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提升软件的扩展性和维护性。设计模式

目的:是提升其可理解性,下降其修改为本。架构

通俗的说法就是,程序的功能和结果没有任何的变化。重构只是对程序内部结构进行调整,让代码更加容易理解,而后更容易维护。也就是代码的优化。ide

经过上述定义,能够看出,重构并非.net的自己的特性,而是软件设计范畴。函数

2.重构的目的性能

 A.改进软件的设计学习

   在实际工做中,为了赶进度或是为了短时间利益,再或者是没有彻底摸清软件总体架构的状况下,对代码进行改动。而这些改动的积累很容易使软件偏离它原先的设计初衷,使软件变很很难维护或没法维护。优化

而重构能够帮助从新组织代码,从新清晰的体现结构和进一步改进设计。

B.提升代码的质量和可维护性

容易理解的代码很容易维护和作进一步开发。即便写这些代码的程序员自己而言,容易更解的代码也能帮助他容易的修改。

代码也是文档,首先是写给人看的,其次才是计算机。

C.帮助尽早的发现错误 

    重构是一个复习和反馈的过程,在另外一个时段从新审视本身或别人的代码,能够更容易发现问题和加深对代码的理解.

重构是一个良好的开发习惯。

D.能够提升开发速度

重构对设计和代码的改进,均可以有效提升开发速度。

在一个有缺陷的设计和混乱的代码基础上开发,即便表面是进度较快,但本质是延后对设计缺陷的发现和对错误的修改。也就延后了开发风险,最终要在开发后期付出更多的代价。

一句话,出来混,早晚是要还的!!

3.重构的时机

重构的时候,即什么时候须要重构,什么时候不须要

A.首先,如下几种状况须要重构:

过大的类和过长的方法

过长的方法因为包含的逻辑过于复杂,错误机率将直线上升,而可读性则直线降低,类的健壮性很容易被打破。当看到一个过长的方 法时,须要想办法将其划分为多个小方法,以便于分而治之。

牵一发而须要动全身的修改

  当你发现修改一个小功能,或增长一个小功能时,就引起一次代码地震,也许是你的设计抽象度不够理想,功能代码太过度散所引发的。

类之间须要过多的通信

  A类须要调用B类的过多方法访问B的内部数据,在关系上这两个类显得有点狎昵,可能这两个类本应该在一块儿,而不该该分家。

过分耦合的信息链

  若是你在代码中看到须要获取一个信息,须要一个类的方法调用另外一个类的方法,层层挂接,就象输油管同样节节相连。这每每是由于衔接层太多形成的,须要查看就否有可移除的中间层,或是否能够提供更直接的调用方法。

各自为政的功能模块

  若是你发现有两个类或两个方法虽然命名不一样但却拥有类似或相同的功能,你会发现每每是由于开发团队成员协调不够形成的。笔者曾经写了一个颇好用的字符串处理类,但由于没有及时通告团队其余人员,后来发现项目中竟然有三个字符串处理类。革命资源是珍贵的,咱们不该各立山头干革命。

不完美的设计  

  每一个系统都或多或少存在不完美的设计,刚开始可能注意不到,到后来才会慢慢凸显出来,此时惟有敢于更改才是最好的出路。

缺乏必要的注释

  虽然许多软件工程的书籍常提醒程序员须要防止过多注释,但这个担忧好象并无什么必要。每每程序员更感兴趣的是功能实现而非代码注释,由于前者更能带来成就感,因此代码注释 每每不是过多而是过少,过于简单。人的记忆曲线降低的坡度是陡得吓人的,当过了一段时间后再回头补注释时,很容易发生"提笔忘字,愈言且止"的情形。

曾在网上看到过微软的代码注释,其详尽程度让人叹为观止,也从中体悟到了微软成功的一个经验。

(以上关于重构的内容来自网上小伙伴的分析,仍是比较全面的,摘录过来分享之)

B.还有几种状况是不适用重构的:

代码混乱,错误百出,这种状况,不是重构而是须要重写了

大型多模块软件,须要逐步重构,不是一会儿完成

重构须要太长的时间,这种状况下不建议重构。

项目即将进入交付阶段,隐定性赛过其它。

3.如何进行重构

前面讲了太多的理论知识,下面来点硬货,说说重构的方法。

3.1使用VS.NET 自身的功能实现快速重构

VS.net自己关于重构的功能,可能不少人不多用到,做为一个重构的辅助功能,虽然说不能彻底实现重构,可是能够帮助咱们快速优化代码。

3.1.1重构类型

<1>. 重命名

<2>.提取方法 

<3>. 封装字段

<4>. 提取接口

<5>. 将局部变量提高为参数

<6>. 移除参数

<7>. 从新排列参数

VS.NET中提供了这么七种重构的类型。咱们在代码编辑窗口中,点击鼠标右键,能够看到以下图所示:

  

下面,咱们逐一说明

<1>重命名

咱们在代码重构过程当中,会有不按规范命名的状况发生或者咱们想让一段代码产生一个副本。

A. 提供了一种重命名代码符号(如字段、局部变量、方法、命名空间、属性和类型)标识符的简单方法.

B. “重命名”功能除了可用来更改标识符的声明和调用之外,还可用来更改注释中和字符串中的名称.

以下图所示,选中一个名称后,输入新名称,VS.NET会提示你更改那些名字。

 

<2>.提取方法

能够经过从现有成员的代码块中提取选定的代码来建立新方法.

B. 建立的新方法中包含选定的代码,而现有成员中的选定代码被替换为对新方法的调用.

C. 代码段转换为其本身的方法,使您能够快速而准确地从新组织代码,以得到更好的重用和可靠性.

• 优势

A. 经过强调离散的可重用方法鼓励最佳的编码作法。

B. 鼓励经过较好的组织得到自记录代码。当使用描述性名称时,高级别方法能够像读取一系列注释同样进行读取。

C. 鼓励建立细化方法,以简化重载。

D. 减小代码重复.

以下图,咱们选中一个方法中的代码片断,点重构中的 “提取方法”弹出下下对话框,咱们重命名一个新的方法名

 

肯定后,以下所示:

 

生成一个静态的方法。在一个方法实现中代码片断太长的时候,咱们能够很方便的进行方法提取了。

<3>. 封装字段

A. 能够从现有字段快速建立属性,而后使用对新属性的引用无缝更新代码.

B. 当某个字段为publicC# 参考)时,其余对象能够直接访问该字段并对其进行修改,而不会被拥有该字段的对象检测到。经过使用属性(C# 编程指南)封装该字段,能够禁止对字段的直接访问。

C. 仅当将光标与字段声明置于同一行时,才能够执行“封装字段”操做。

• 实例

大部分开发者都习惯把类级的变量(字段)暴露给外界。因为每个对象都属于面向对象编程,因此开发者应该容许经过属性或方法来存取变量。这种状况可使用重构菜单下的"封装字段"选项来进行处理。

为此,选择你想包装在一个属性中的类级变量而且选择"封装字段"选项。这将打开一个以下图所示的对话框:

你须要输入该属性的名字而且决定是否你想从类外或类内部更新到该变量的参考。就象"重命名"对话框同样,你能够在应用以前先预览一下所做的改变。

以下图所示,假如咱们要在动物这个类中,加一个属性,咱们使用封装字段,

 

 

若是选择“外部”肯定后,代码以下:

 

能够看到,为咱们自动增长了一个外部属性

<4>• 提取接口

A. 使用来自现有类、结构或接口的成员建立新接口的简单方法.

B. 当几个客户端使用类、结构或接口中成员的同一子集时,或者当多个类、结构或接口具备通用的成员子集时,在接口中嵌入成员子集将颇有用.

C. 仅当将光标定位于包含要提取成员的类、结构或接口中时,才能够访问此功能。当光标处于此位置时,调用“提取接口”重构操做.

以下图所示,咱们在类名称点击右键 重构,选择提取接口,在弹出窗口中,输入接口名称,选择类的公有成员,则为它们建立了一个接口文件,很是实用。

 

<5>• 将局部变量提高为参数

A. 提供一种简单的方法,以在正确更新调用站点的同时将变量从局部使用移动至方法、索引器或构造函数参数.

B. 调用“将局部变量提高为参数”操做时,变量将被添加到成员参数列表的结尾处.

C. 对已修改为员的全部调用都将使用新参数(将替代最初赋给该变量的表达式)当即进行更新,并保留代码,以使其像变量提高以前那样正常工做.

D. 将常数值赋值给提高的变量时,此重构操做效果最好。必须声明并初始化该变量,而不能仅声明或仅赋值.

• 实例

原代码:

private static void NewMethod2()
{
        string s = "";
}

选中s,转换后

private static void NewMethod2(string s)
{ 
} 

 

<6>• 移除参数

A. 从方法、索引器或委托中移除参数的简单方法.

B. 在调用成员的任何位置,都会将参数移除以反映新声明.

• 实例

原代码    

protected void Page_Load(EventArgs e, object sender)
{
        int i = 0;
        NewMethod2("1","2");
}

private static void NewMethod2(string s1, string s2)
{
        string s = s1 + s2;
 }

移除后的代码   

 protected void Page_Load(EventArgs e, object sender)
{
        int i = 0;
        NewMethod2();
 }

 private static void NewMethod2()
{
        string s = s1 + s2;
}

 

<7>• 从新排列参数

A. 对方法、索引器和委托的参数顺序进行更改的简单方法.

B. 能够经过方法声明或方法调用来从新排列参数。要将光标置于方法声明或委托声明中,而不是置于正文中。

• 实例

原代码:

private static void NewMethod2(string s1,string s2)
{
}

从新排列后

private static void NewMethod2(string s2,string s1)
{

}

4.重构实例

 咱们经过一个实例来看看重构带来的好处,仍是咱们前一节的关于动物叫的例子,有一个基类 动物(Animal)有成员属性名字(Name

方法叫声(Shout)和叫的次数的虚方法(getShoutCount),它有N个派生类,咱们先看重构前的代码以下:

  1 /// <summary>
  2 /// 动物类(父类)
  3 /// </summary>
  4 class Animal
  5 {
  6     /// <summary>
  7     /// 名字
  8     /// 说明:类和子类可访问
  9     /// </summary>
 10     protected string name;
 11 
 12 
 13     /// <summary>
 14     /// 构造函数
 15     /// </summary>
 16     /// <param name="name"></param>
 17     public Animal(string name)
 18     {
 19         this.name = name;
 20     }
 21 
 22     private int shoutNum = 3;
 23     public int ShoutNum
 24     {
 25         get { return shoutNum; }
 26         set { shoutNum = value; }
 27     }
 28 
 29     /// <summary>
 30     /// 名字(虚属性)
 31     /// </summary>
 32     public virtual string MyName
 33     {
 34         get { return this.name; }
 35 
 36     }
 37 
 38     /// <summary>
 39     /// 叫(虚方法)
 40     /// </summary>
 41     public virtual void Shout()
 42     {
 43         Console.WriteLine("我会叫!");
 44     }
 45 
 46 }
 47 
 48 /// <summary>
 49 /// 狗(子类)
 50 /// </summary>
 51 class Dog : Animal
 52 {
 53     string myName;
 54     public Dog(string name)
 55         : base(name)
 56     {
 57         myName = name;
 58     }
 59 
 60     /// <summary>
 61     /// 名字(重写父类属性)
 62     /// </summary>
 63     public override string MyName
 64     {
 65         get { return "我是:狗狗,我叫:" + this.name; }
 66     }
 67 
 68     /// <summary>
 69     /// 叫(重写父类方法)
 70     /// </summary>
 71     public override void Shout()
 72     {
 73         string result = "";
 74         for (int i = 0; i < ShoutNum; i++)
 75             result += "汪!";
 76         Console.WriteLine(result);
 77     }
 78 }
 79 /// <summary>
 80 /// 猫(子类)
 81 /// </summary>
 82 class Cat : Animal
 83 {
 84     string myName;
 85     public Cat(string name)
 86         : base(name)
 87     {
 88         myName = name;
 89     }
 90     /// <summary>
 91     /// 名字(重写父类属性)
 92     /// </summary>
 93     public override string MyName
 94     {
 95         get { return "我是:猫咪,我叫:" + this.name; }
 96 
 97     }
 98 
 99     /// <summary>
100     /// 叫(重写父类方法)
101     /// </summary>
102     public override void Shout()
103     {
104         string result = "";
105         for (int i = 0; i < ShoutNum; i++)
106             result += "喵!";
107         Console.WriteLine(result);
108     }
109 }
110 
111 /// <summary>
112 /// 羊(子类)
113 /// </summary>
114 class Sheep : Animal
115 {
116     string myName;
117     public Sheep(string name)
118         : base(name)
119     {
120         myName = name;
121     }
122     /// <summary>
123     /// 名字(重写父类属性)
124     /// </summary>
125     public override string MyName
126     {
127         get { return "我是:羊羊,我叫:" + this.name; }
128 
129     }
130 
131     /// <summary>
132     /// 叫(重写父类方法)
133     /// </summary>
134     public override void Shout()
135     {
136         string result = "";
137         for (int i = 0; i < ShoutNum; i++)
138             result += "咩!";
139         Console.WriteLine(result);
140     }
141 }

 

咱们能够看到,虽然这段代码实现了继承和多态,封装的特性,代码仍是比较简洁的,可是有一点就是这个叫的方法,每一个子类中都要写一次循环。假如又来了猪啊,牛啊,这些动物,是否是代码量也很多啊。咱们能不能只写一次循环呢,答案是确定的,看咱们重构后的代码:

  1 /// <summary>
  2 /// 动物类(父类)
  3 /// </summary>
  4 class Animal
  5 {
  6     /// <summary>
  7     /// 名字
  8     /// 说明:类和子类可访问
  9     /// </summary>
 10     protected string name;
 11 
 12     /// <summary>
 13     /// 构造函数
 14     /// </summary>
 15     /// <param name="name"></param>
 16     public Animal(string name)
 17     {
 18         this.name = name;
 19     }
 20 
 21     private int shoutNum = 3;
 22     public int ShoutNum
 23     {
 24         get { return shoutNum; }
 25         set { shoutNum = value; }
 26     }
 27 
 28     /// <summary>
 29     /// 名字(虚属性)
 30     /// </summary>
 31     public virtual string MyName
 32     {
 33         get { return this.name; }
 34 
 35     }
 36 
 37     /// <summary>
 38     /// 叫声,这个方法去掉虚方法,把循环写在这里
 39     /// </summary>
 40     public void Shout()
 41     {
 42         string result = "";
 43         for (int i = 0; i < ShoutNum; i++)
 44             result += getShoutSound()+"";
 45 
 46         Console.WriteLine(MyName);
 47         Console.WriteLine(result);
 48     }
 49     /// <summary>
 50     /// 建立一个叫声的虚方法,子类重写
 51     /// </summary>
 52     /// <returns></returns>
 53     public  virtual string  getShoutSound()
 54     {
 55         return "";
 56     }           
 57 }
 58 
 59 /// <summary>
 60 /// 狗(子类)
 61 /// </summary>
 62 class Dog : Animal
 63 {
 64     string myName;
 65     public Dog(string name): base(name)
 66     {
 67         myName = name;
 68     }
 69     /// <summary>
 70     /// 名字(重写父类属性)
 71     /// </summary>
 72     public override string MyName
 73     {
 74         get { return "我是:狗狗,我叫:" + this.name; }
 75     }        
 76     /// <summary>
 77     /// 叫(重写父类方法)
 78     /// </summary>
 79     public override string getShoutSound()
 80     {
 81         return "汪!";           
 82     }
 83 }
 84 /// <summary>
 85 /// 猫(子类)
 86 /// </summary>
 87 class Cat : Animal
 88 {
 89     string myName;
 90     public Cat(string name): base(name)
 91     {
 92         myName = name;
 93     }
 94     /// <summary>
 95     /// 名字(重写父类属性)
 96     /// </summary>
 97     public override string MyName
 98     {
 99         get { return "我是:猫咪,我叫:" + this.name; }
100     }
101     /// <summary>
102     /// 叫(重写父类方法)
103     /// </summary>
104     public override string getShoutSound()
105     {
106         return "喵!";
107     }
108 }
109 
110 /// <summary>
111 /// 羊(子类)
112 /// </summary>
113 class Sheep : Animal
114 {
115     string myName;
116     public Sheep(string name): base(name)
117     {
118         myName = name;
119     }
120     /// <summary>
121     /// 名字(重写父类属性)
122     /// </summary>
123     public override string MyName
124     {
125         get { return "我是:羊羊,我叫:" + this.name; }
126     }
127     /// <summary>
128     /// 叫(重写父类方法)
129     /// </summary>
130     public override string getShoutSound()
131     {
132         return "咩!";
133     }
134 }

 

这样重构,是否是代码量就少不少了,结构也更加清晰了。。

调用一:            

//调用
Animal sheep = new Sheep("美羊羊");
sheep.Shout();
Console.ReadLine();

结果以下:

//调用结果
//我是:羊羊,我叫:美羊羊
//咩!咩!咩!

调用二:          

//调用
Animal dog= new Dog("旺财");
dog.Shout();
Console.ReadLine();

结果以下: 

//调用结果
//我是:狗狗,我叫:旺财
//汪!汪!汪!

 

总结:重构是一门复杂的学问,本节内容只是重构的皮毛而已,有一些书籍用几千页的篇幅来介绍中重构。可否熟练使用重构,写出优雅高效的代码是区分一个程序员优秀的标准之一,重构也是学习设计模的基础,这须要咱们不断的练习和思考才能作好。

 

要点:

A.重构(Refactoring)就是在不改变软件现有功能的基础上,经过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提升软件的扩展性和维护性。

B.重构不是.NET面向对象自己的特性,而属于一种软件设计范畴。

C.重构提升了代码的可读性,可维护性;也使得代码结构更加清晰。

D.可否有效的重构代码,是一个程序员优秀与否的标准之一。也是学习设计模式和软件架构的基础。

E.重构是一门代码艺术。 

============================================================================================== 

返回目录

 <若是对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>
 

==============================================================================================  

相关文章
相关标签/搜索