代码看重

 

 

连锁作品连接

重力之源:代码中的泵

大观:梳理编程约定

编制程序之基础:数据类型(1)

编制程序之基础:数据类型(贰)

可复用代码:组件的全进程

首要:委托与事件

物以类聚:对象也有生命

难免的两难:代码正视

  • 1贰.一 从面向对象开端
    • 1贰.壹.1 对象基础:封装
    • 12.一.二 对象增加:继承
    • 1二.一.三 对象行为:多态
  • 1二.贰 不可防止的代码正视
    • 1二.二.1 依赖存在的因由
    • 1二.二.二 耦合与内聚
    • 12.二.3 依赖造成的两难
  • 1二.叁 降低代码正视
    • 1二.3.1 认识抽象与具象
    • 1二.叁.二 再看“依赖倒置原则”
    • 12.叁.3 正视注入
  • 1二.四 框架中的“代码倚重”
    • 1二.肆.一 控制转换
    • 12.4.二 信赖注入对框架的意思
  • 12.5 本章回想
  • 12.陆 本章思虑

在广阔无垠的代码世界中,有着许多的指标,跟人和人之间有社交关系壹样,对象跟对象时期也防止不了接触,所谓接触,正是指多少个指标要运用到别的对象的天性、方法等成员。现实生活中一位的张罗关系千头万绪可能并不是怎么样倒霉的事务,不过对于代码中的对象而言,复杂的”社交关系”往往是不提倡的,因为对象时期的关联性越大,意味着代码改动壹处,影响的范围就会越大,而那统统不方便人民群众系统重构和末代维护。所以在现世软件开发进程中,大家应该遵照”尽量下降代码注重”的规则,所谓尽量,就曾经说北宋码依赖不可避免。

有时一味地追求”下降代码正视”反而会使系统越来越错综复杂,我们不可能不在”降低代码注重”和”扩展系统规划复杂”之间找到1个平衡点,而不该去盲目追求”五个人定理”那种设计境界。

    注:”多个人定理”指:任何多个人以内的涉嫌带,基本规定在四个人左右。多个目生人之间,能够经过多少人来建立联系,此为多个人定律,也称作四个人法则。

12.壹 从面向对象开头

在微型总结机科学和技术升高历史中,编制程序的法子直接都是趋向于不难化、人性化,”面向对象编制程序”正是历史升高某一等级的产物,它的产出不仅是为着提高软件开发的频率,还适合人们对代码世界和真实性世界的合并认识观。当谈到”面向对象”,出现在大家脑海中的词无非是:类,抽闲,封装,继承以及多态,本节将从指标基础、对象扩充以及对象行为四个地点对”面向对象”做出解释。

    注:面向对象中的”面向”二字意指:在代码世界中,大家相应将其余东西都看做成2个封闭的单元,那么些单元正是”对象”。对象不仅能够表示叁个方可看得见摸得着的实体,它仍可以够象征二个抽象进度,从理论上讲,任何具体的、抽象的事物都能够定义成三个对象。

1二.一.1 对象基础:封装

和具体世界一样,无论从微观上如故宏观上看,那几个世界均是由许许多多的单个独立物体组成,小到人、器官、细胞,大到国家、星球、宇宙,
各类独立单元都有协调的属性和行为。仿照现实世界,大家将代码中有关联性的数码与操作合并起来形成一个完整,之后在代码中数据和操作均是以3个完好出现,那个进程称为”封装”。封装是面向对象的基本功,有了打包,才会有总体的定义。

发展历史 1

图1贰-一 封装前后

如上海体育场所12-一所示,图中上手部分为包装之前,数据和操作数据的方式未有相互对应涉及,方法能够访问到此外三个数额,种种数据尚未访问限制,显得杂乱;图中左边部分为包装之后,数据与之提到的点子形成了叁个1体化单元,大家称为”对象”,对象中的方法操作同1对象的数码,数据里面有了”爱慕”边界。外界能够通过对象暴光在外的接口访问对象,比如给它发送新闻。

一般性状态下,用于保存对象数据的有字段和性质,字段壹般设为私有访问权限,只准对象内部的秘诀访问,而属性1般设为公开访问权限,供外界访问。方法正是目的的呈现作为,分为个人访问权限和公开访问权限两类,前者只准对象内部访问,而后者允许外界访问。

发展历史 2

 1 //Code 12-1
 2 class Student //NO.1
 3 {
 4     private string _name; //NO.2
 5     private int _age;
 6     private string _hobby;
 7     public string Name //NO.3
 8     {
 9         get
10         {
11             return _name;
12         }
13     }
14     public int Age
15     {
16         get
17         {
18             return _age;
19         }
20         set
21         {
22             if(value<=0)
23             {
24                 value=1;
25             }
26             _age = value;
27         }
28     }
29     public string Hobby
30     {
31         get
32         {
33             return _hobby;
34         }
35         set
36         {
37             _hobby = value;
38         }
39     }
40     public Student(string name,int age,string hobby)
41     {
42         _name = name;
43         _age = age;
44         _hobby = hobby;
45     }
46     public void SayHello() //NO.4
47     {
48         Console.WriteLine(GetSayHelloWords());
49     }
50     protected virtual string GetSayHelloWords() //NO.5
51     {
52         string s = "";
53         s += "hello,my name is " + _name + ",\r\n",
54         s += "I am "+_age + "years old," + "\r\n";
55         s += "I like "+_hobby + ",thanks\r\n";
56         return s;
57     }
58 }

发展历史 3

地点代码Code
1二-一将学生这厮群定义成了三个Student类(NO.一处),它含有八个字段:分别为保存姓名的_name、保存年龄的_age以及保存爱好的_hobby字段,那四个字段都以私有访问权限,为了便于外界访问内部的多寡,又各自定义了八个属性:分别为访问姓名的Name,注意该属性是只读的,因为健康景况下姓名不能够再被外界改变;访问年龄的Age,注意当给年龄赋值小于等于0时,代码自动将其设置为一;访问爱好的Hobby,外界能够通过该属性对_hobby字段进行完全访问。同时Student类包涵八个办法,贰个通晓的SyaHello()方法和1个受保证的GetSayHelloWords()方法,前者肩负输出对象本身的”介绍音信”,后者负责格式化”介绍音讯”的字符串。Student类图见图1贰-二:

发展历史 4

图12-2 Student类图

    注:上文中校类的分子访问权限只分为多少个部分,四个对外围可知,包括public;另1种对外场不可知,包含private、protected等。

注意类与对象的区分,假诺说对象是代码世界对现实世界中各个东西的顺序映射,那么类正是这一个映射的沙盘,通过沙盘创造具体的投射实例:

发展历史 5

图1二-叁 对象实例化

咱俩得以观察代码Code
1二-第11中学的Student类既包罗个人成员也带有公开成员,私有成员对外边不可知,外界如需访问对象,只好调用给出的通晓方法。那样做的目标正是将外界不要要精晓的新闻隐藏起来,对外只提供简单的、易懂的、稳定的公然接口即可方便外界对该项目标利用,同时也幸免了外围对目标内部数据不须求的改动和访问所导致的尤其。

包装的准则:

包裹是面向对象的率先步,有了包装,才会有类、对象,再才能谈继承、多态等。经过前人充足的施行和计算,对封装有以下规则,大家在平日事实上开支中应有尽只怕遵守这几个规则:

1)1个品种应该尽只怕少地展露自身的内部音信,将细节的1些隐藏起来,只对伯公开供给的安定的接口;同理,八个体系应该尽恐怕少地询问其余系列,这正是常说的”迪米特法则(Law
of
德姆eter)”,迪米特法则又被称作”最小知识标准化”,它强调3个门类应该尽也许少地理解其余类型的中间贯彻,它是降低代码重视的二个首要指引思想,详见本章后续介绍;

贰)理论上,三个门类的其中代码能够轻易改变,而不该影响对曾外祖父开的接口。这就须求大家将”善变”的一对隐藏到花色内部,对曾祖父开的终将是相对平稳的;

三)封装并不单指代码层面上,如类型中的字段、属性以及艺术等,越来越多的时候,我们能够将其应用到系统结构层面上,八个模块乃至系统,也理应只对外提供稳定的、易用的接口,而将现实完成细节隐藏在系统内部。

包装的意义:

卷入不仅能够有利于对代码对数据的汇合管理,它还有以下意义:

1)封装隐藏了类其他现实贯彻细节,保险了代码安全性和安宁;

二)封装对外界只提供稳定的、易用的接口,外部使用者不供给过多地询问代码达成原理也不须要控制复杂难懂的调用逻辑,就可知很好地采纳项目;

叁)封装保证了代码模块化,提升了代码复用率并保障了系统机能的分别。

发展历史,1二.一.2 对象扩充:继承

包裹强调代码合并,封装的结果正是开创2个个独立的卷入件:类。那么大家有未有任何的点子去创制新的包裹件呢?

在现实生活中,1种物体往往衍生自其余1种物体,所谓衍生,是指衍生体在具有被衍生体的习性基础上,还持有任何额外的性子,被衍生体往往更抽象,而衍生体则更现实,如高校衍生自高校,因为高校具备高校的风味,但高校又比学校实际,人衍生自生物,因为人持有生物的性状,但人又比生物具体。

发展历史 6

图1二-4 高校衍生图

如上海图书馆1二-四,学校相对来讲最抽象,高校、高中以及小学均能够衍生自高校,进一步来看,大学其实也比较空虚,因为大学还足以有切实的本科、专科,由此本科和专科学校能够衍生自大学,当然,抽象和现实性的概念是周旋的,借使你认为本科还不够具体,那么它能够再衍生出来壹本、二本以及三本。

在代码世界中,也设有”衍生”那壹说,从1个较肤浅的类型衍生出1个较具体的类型,大家称”后者派生自前者”,借使A类型派生自B类型,那么称那些历程为”继承”,A称之为”派生类”,B则称之为”基类”。

    注:派生类又被形象地誉为”子类”,基类又被形象地喻为”父类”。

在代码1贰-第11中学的Student类基础上,如若大家必要创立七个博士(College_Student)的连串,那么大家完全能够从Student类派生出3个新的硕士类,因为大学生具备学生的风味,但又比学生更切实:

发展历史 7

 1 //Code 12-2
 2 class College_Student:Student //NO.1
 3 {
 4     private string _major;
 5     public string Major
 6     {
 7         get
 8         {
 9             return _major;
10         }
11         set
12         {
13             _major = value;
14         }
15     }
16     public College_Student(string name,int age,string hobby,string major) :base(name,age,hobby) //NO.2
17     {
18         _major = major;
19     }
20     protected override string GetSayHelloWords() //NO.3
21     {
22         string s = "";
23         s += "hello,my name is " + Name + ",\r\n",
24         s += "I am "+ Age + "years old, and my major is " + _major + ",\r\n";
25         s += "I like "+ Hobby + ", thanks\r\n";
26         return s;
27     }
28 }

发展历史 8

如上代码Code
1②-二所示,College_Student类继承Student类(NO.1处),College_Student类具备Student类的习性,比如Name、Age以及Hobby,同时College_Student类还增添了额外的正规(Major)属性,通过在派生类中重写GetSyaHelloWords()方法,我们重新格式化”个人音讯”字符串,让其包涵”专业”的音信(NO.3处),最后,调用College_Student中从基类继承下来的SayHello()方法,便得以轻松输出自身的个人新闻。

我们看看,派生类通过持续取得了基类的全套音信,之外,派生类仍是能够追加新的剧情(如College_Student类中新增的Major属性),基类到派生类是八个虚幻到实际的进程,因而,大家在布置项指标时候,平常将通用部分提取出来,形成3个基类,今后全数与基类有种族关系的品种均能够继承该基类,以基类为根基,扩展自身特有的质量。

发展历史 9

图12-5 College_Student类继承图

局地时候,1种档次只用于其余门类派生,一贯不须要创制它的某部具体指标实例,那样的类高度抽象化,咱们称那体系为”抽象类”,抽象类不担当创设具体的靶子实例,它含有了派生类型的一头成分。除了通过再而三有个别项目来创建新的门类,.NET中还提供此外壹种类似的成立新品类的诀窍:接口达成。接口定义了一组方法,全体完成了该接口的花色必须兑现接口中存有的艺术:

发展历史 10

 1 //Code 12-3
 2 interface IWalkable
 3 {
 4     void Walk();
 5 }
 6 class People:IWalkable
 7 {
 8     //…
 9     public void Walk()
10     {
11         Console.WriteLine("walk quickly");
12     }
13 }
14 class Dog:IWalkable
15 {
16     //…
17     public void Walk()
18     {
19         Console.WriteLine("walk slowly");
20     }
21 }

发展历史 11

如上代码Code
1二-3所示,People和Dog类型均贯彻了IWalkable接口,那么它们必须都落到实处IWalkable接口中的Walk()方法,见下图1二-陆:

发展历史 12

图1二-6 接口继承

继承包含三种方法,一种为”类继承”,1种为”接口继承”,它们的意义类似,都是在存活项目基础上创办出新的项目,可是它们也有分别:

一)类继承强调了族群关系,而接口继承强调通用功效。类继承中的基类和派生类属于祖宗和后代的关联,而接口继承中的接口和促成了接口的门类并不曾那种关联。

二)类继承强调”笔者是(Is-A)”的涉及,派生类”是”基类(注意那里的”是”代表派生类具备基类的特点),而接口继承强调”小编能做(Can-Do)”的涉嫌,实现了接口的门类具有接口中规定的行为能力(由此接口在命名时均以”able”作为后缀)。

三)类继承中,基类即便较肤浅,可是它能够有现实的贯彻,比如方法、属性的落实,而接口继承中,接口不允许有别的的具体贯彻。

接二连三的规则:

持续是面向对象编制程序中开创项指标1种艺术,在包装的基本功上,它亦可收缩工作量、提升代码复用率的同时,神速地开创出全体相似性的门类。在应用持续时,请根据以下规则:

一)严酷服从”里氏替换原则”,即基类出现的地点,派生类一定能够出现,由此,不要盲目地去采取持续,假使多个类未有衍生的涉嫌,那么就不该有继承关系。假如让猫(Cat)类派生自狗(Dog)类,那么很简单就足以见见,狗类出现的地点,猫类不肯定能够取代它出现,因为它两一直就从未有过抽象和具体的层次关系。

贰)由于派生类会继承基类的全部内容,所以要严控好项目标接轨层次,不然派生类的容积会越来越大。其余,基类的改动必然会影响到派生类,继承层次太多科管,继承是增添耦合的最要紧成分。

3)继承强调类型之间的习性,而非天性。由此大家壹般将品种都独具的一些提取出来,形成贰个基类(抽象类)可能接口。

12.一.3 对象行为:多态

“多态”1词来源生物学,本意是指地球上的享有生物展现出形象和气象的多种性。在面向对象编制程序中多态是指:同一操作效用于不一样类的实例,将生出不相同的实践结果,即区别类的目的吸收一模一样的消息时,获得分裂的结果。

多态强调面向对象编制程序中,对象的有余显示作为,见下代码Code 1二-4:

发展历史 13

 1 //Code 12-4
 2 class Student //NO.1
 3 {
 4     public void IntroduceMyself()
 5     {
 6         SayHello();
 7     }
 8     protected virtual void SayHello()
 9     {
10         Console.WriteLine("Hello,everyone!");
11     }
12 }
13 class College_Student:Student //NO.2
14 {
15     protected override void SayHello()
16     {
17         base.SayHello();
18         Console.WriteLine("I am a college student…");
19     }
20 }
21 class Senior_HighSchool_Student:Student //NO.3
22 {
23     protected override void SayHello()
24     {
25         base.SayHello();
26         Console.WriteLine("I am a senior high school student…");
27     }
28 }
29 class Program
30 {
31     static void Main()
32     {
33         Console.Title = "SayHello";
34         Student student = new Student();
35         student.IntroduceMyself(); //NO.4
36         student = new College_Student();
37         student.IntroduceMyself(); //NO.5
38         student = new Senior_HighSchool_Student();
39         student.IntroduceMyself(); //NO.6
40         Console.Read();    
41     }
42 }

发展历史 14

如上代码Code
12-四所示,分别定义了三个类:Student(NO.一处)、College_Student(NO.2处)、Senior_HighSchool_Student(NO.三处),前边多少个类继承自Student类,同等看待写了SayHello()方法。在客户端代码中,对于同壹行代码”student.IntroduceMyself();”而言,一次调用(NO.四、NO.五以及NO.六处),显示器输出的结果却分歧等:

发展历史 15

图12-柒 多态效果

如上海教室1贰-七所示,3遍调用同一个措施,不一样指标有不一样的呈现作为,大家称为”对象的多态性”。从代码Code
12-4中得以观望,之所以出现一样的调用会生出区别的显示作为,是因为给基类引用student赋值了区别的派生类对象,并且派生类中重写了SayHello()虚方法。

对象的多态性是以”继承”为前提的,而继续又分为”类继承”和”接口继承”两类,那么多态性也有二种情势:

壹)类继承式多态;

类继承式多态须求虚方法的参与,正如代码Code
1二-四中那么,派生类在必要时,必须重写基类的虚方法,最终动用基类引用调用各样派生类对象的点子,达到种种展现作为的效能:

2)接口继承式多态。

接口继承式多态不要求虚方法的出席,在代码Code 1二-三的底子上编写制定如下代码:

发展历史 16

 1 //Code 12-5
 2 class Program
 3 {
 4     static void Main()
 5     {
 6         Console.Title = "Walk";
 7         IWalkable iw = new People();
 8         iw.Walk(); //NO.1
 9         iw = new Dog();
10         iw.Walk(); //NO.2
11         Console.Read();
12     }
13 }

发展历史 17

如上代码Code
1贰-伍所示,对于同1行代码”iw.Walk();”的五回调用(NO.1和NO.2处),有例外的显示作为:

发展历史 18

图1二-8 接口继承式多态

在面向对象编制程序中,多态的前提是后续,而后续的前提是包裹,三者缺1不可。多态也是是降低代码注重的无敌有限帮忙,详见本章后续有关内容。

1贰.二 不可幸免的代码重视

本书前边章节曾介绍过,程序的实践进度正是方法的调用进程,有方法调用,必然会促使对象跟对象之间爆发依赖性,除非三个目的不参与程序的运作,那样的靶子就如一座孤岛,与任何对象未有任何交互,然而这么的指标也就从未有过其余存在价值。因而,在我们的程序代码中,任何3个对象自然会与别的八个照旧更多少个指标产生注重性关系。

1二.二.壹 信赖存在的缘故

“方法调用”是最普遍发生注重的由来,三个对象与别的对象自然会通讯(除非大家把持有的代码逻辑全体写在了这一个目的内部),通讯通常状态下就象征有法子的调用,有法子的调用就代表那多少个指标之间存在依靠关系(至少要有其余对象的引用才能调用方法),其余常见的一种发生依赖性的原由是:继承,没有错,继承尽管给我们带来了尤其大的功利,却也给我们带来了代码正视。重视发生的来由大概能够分以下四类:

1)继承;

派生类继承自基类,得到了基类的整体内容,但同时,派生类也受控于基类,只要基类发生变动,派生类一定发生变化:

发展历史 19

图1二-九 继承注重

上图12-九中,B和C继承自A,A类改变势必会影响B和C的成形。

二)成员对象;

贰个项目涵盖别的七个项目的分巳时,前者必然受控于后世,纵然后者的更改不肯定会潜移默化到前者:

发展历史 20

图1二-十 成员对象重视

如上图1贰-10,A包涵B类型的分子,那么A就受控于B,B在A内部完全可知。

    注:成员对象依赖跟组合(聚合)类似。

三)传递参数;

二个项目作为参数字传送递给其余八个项指标积极分子方法,那么后者肯定会受控于前者,就算前者的更改不必然会潜移默化到后者:

发展历史 21

图12-1一 传参注重

如上海体育场地12-1一,A类型的主意Method()包含二个B类型的参数,那么A就受控于B,B在A的Method()方法可知。

四)权且变量。

别的时候,四个门类将别的三个门类用作了如今变量时,那么前者就受控于后者,即便后者的改观不必然会影响到前端:

发展历史 22

 1 //Code 12-6
 2 class A
 3 {
 4     public void DoSomething()
 5     {
 6         //…
 7     }
 8 }
 9 class B
10 {
11     public void DoSomething()
12     {
13         //…
14         A a = new A();
15         a.DoSomething();
16         //…
17     }
18 }

发展历史 23

如上代码Code
1二-陆,B的DoSomething()方法中利用了A类型的一时对象,A在B的DoSomething()方法中部分范围可知。

平常状态下,通过被注重者在注重者内部可见范围大小来衡量正视程度的音量,原因很简短,可知范围越大,表明访问它的票房价值就越大,重视者受影响的可能率也就越大,由此,上述多种信赖发生的来由中,重视程度按顺序依次降低。

1二.贰.二 耦合与内聚

为了度量对象之间信赖程度的高低,大家引进了”耦合”这一定义,耦合度越高,表明对象之间的重视程度越高;为了衡量对象独立性的高低,大家推荐了”内聚”这一概念,内聚性越高,表达对象与外面交互越少、独立性越强。很明显,耦合与内聚是五个彼此对峙又密切相关的定义。

    注:从广义上讲,”耦合”与”内聚”不仅符合对象与对象时期的涉及,也契合模块与模块、系统与系统里头的关系,那眼前面讲”封装”时强调”封装”不仅仅指代码层面上的道理同样。

“模块功用集中,模块之间界限泾渭显著”一向是软件设计追求的对象,软件系统不会因为须求的更改、功用的进步而不得不大范围修改原来已有的源代码,换句话说,大家在软件设计中,应该严谨遵照”高内聚、低耦合”的口径。下图1二-1贰显得多个连串依照该规范上下:

发展历史 24

图12-12 高内聚、低耦合

如上图12-1二所示,”高内聚、低耦合”强调对象与对象时期(模块与模块之间)尽大概多地回落信赖程度,每一种对象(或模块,下同)尽恐怕进步协调的独立性,那就供给它们各自负责的效用相对集中,代码结构由”开放”转向”收敛”。

“职分单壹原则(S景逸SUVP)”是增高对象内聚性的理论指导思想之①,它提议各种对象只承担某3个(1类)效能。

1二.贰.三 依赖造成的”窘迫”

要是在软件系统规划初期,未有合理地下跌(甚至幸免)代码间的耦合,系统开发后期往往会遇上中期不可预料的不方便。下边举例表明注重给大家造成的”狼狈”。

假设三个即将付出的系统中运用到了数据库,系统设计阶段鲜明使用SQL
Server数据库,根据”代码模块化能够提升代码复用性”的尺码,大家将造访SQL
Server数据库的代码封装成了2个单身的类,该类只担负访问SQLServer数据库那一效率:

发展历史 25

 1 //Code 12-7
 2 class SQLServerHelper //NO.1
 3 {
 4     //…
 5     public void ExcuteSQL(string sql)
 6     {
 7         //…
 8     }
 9 }
10 class DBManager //NO.2
11 {
12     //…
13     SQLServerHelper _sqlServerHelper; //NO.3
14     public DBManager(SQLServerHelper sqlServerHelper)
15     {
16         _sqlServerHelper = sqlServerHelper;
17     }
18     public void Add() //NO.4
19     {
20         string sql = "";
21         //…
22         _sqlServerHelper.ExcuteSQL(sql);
23     }
24     public void Delete() //NO.5
25     {
26         string sql = "";
27         //…
28         _sqlServerHelper.ExcuteSQL(sql);
29     }
30     public void Update() //NO.6
31     {
32         string sql = "";
33         //…
34         _sqlServerHelper.ExcuteSQL(sql);
35     }
36     public void Search() //NO.7
37     {
38         string sql = "";
39         //…
40         _sqlServerHelper.ExcuteSQL(sql);
41     }
42 }

发展历史 26

如上代码Code 1二-7所示,定义了贰个SQL
Server数据库访问类SQLServerHelper(NO.一处),该类专责访问SQL
Server数据库,如进行sql语句(别的功能略),然后定义了3个数据库管理类DBManager(NO.2处),该类负责壹些数据的增加和删除改查(NO.四、NO.伍、NO.6以及NO.七处),同时此类还隐含1个SQLServerHelper类型成员(NO.3处),负责具体SQL
Server数据库的拜会。SQLServerHelper类和DBManager类的关系见下图1二-一三:

 

发展历史 27

图1二-1叁 注重于实际

如上图1贰-壹3所示,DBManager类正视于SQLServerHelper类,后者在前端内部完全可知,当DBManager必要拜访SQL
Server数据库时,能够交给SQLServerHelper类型成员负责,到此停止,这多少个品类合营得可怜好,不过,现在一旦大家对数据库的供给爆发变化,不再动用SQL
Server数据库,而供给更改使用MySQL数据库,那么我们须要做些什么工作吧?和事先一样,大家须求定义一个MySQLHelper类来担负MySQL数据库的拜访,代码如下:

发展历史 28

1 //Code 12-8
2 class MySQLHelper
3 {
4     //…
5     public void ExcuteSQL(string sql)
6     {
7         //…
8     }
9 }

发展历史 29

如上代码Code
1二-八,定义了二个特地访问MySQL数据库的体系MySQLHelper,它的布局跟SQLServerHelper相同,接下去,为了使本来已经工作平常的种类重新适应于MySQL数据库,大家还非得逐一修改DBManager类中享有对SQLServerHelper类型的引用,将其全方位立异为MySQLHelper的引用。如若只是3个DBManager类使用到了SQLServerHelper的话,整个更新工作量还不算11分多,但一旦程序代码中还有其余地点采用到了SQLServerHelper类型的话,这几个工作量就多量,除了这几个之外,大家那样做出的具有操作完全背离了软件设计中的”开闭原则(OCP)”,即”对扩展开放,而对修改关闭”。很强烈,大家在追加新的类型MySQLHelper时,还修改了系统原本代码。

并发上述所说难点的重大缘由是,在系统规划初期,DBManager这么些类型信赖了3个切实可行品种SQLServerHelper,”具体”就象征不可改变,同时也就评释五个品类之间的正视关系曾经抵达了”非你不可”的程度。要缓解以上难题,供给我们在软件设计初期就做出一定的措施,详见下一小节。

12.三 下降代码依赖

上1节末尾谈到了代码信赖给我们办事带来的麻烦,还涉及了关键原因是目的与对象时期(模块与模块,下同)信赖关系太过紧密,本节首要表达什么去下跌代码间的依靠程度。

12.3.1 认识”抽象”与”具体”

实质上本书从前好些地方业已冒出过”具体”和”抽象”的词眼,如”具体的类别”、”重视于肤浅而非具体”等等,到最近结束,本书还并不曾系统地介绍这三头的切实意思。

所谓”抽象”,即”不明明、未知、可转移”的情趣,而”具体”则是倒转的含义,它代表”明显、不可更改”。大家在前面讲”继承”时就说过,派生类继承自基类,就是贰个”抽象到现实”的进度,比如基类”动物(Animal)”便是一个虚幻的事物,而从基类”动物(Animal)”派生出来的”狗(Dog)”就是一个现实的事物。抽象与实际的涉及如下图12-14:

发展历史 30

图12-1四 抽象与实际的相对性

    注:抽象与现实也是一个针锋相对的概念,并不能够说”动物”就自然是1个架空的东西,它与”生物”进行比较,正是2个针锋相对具体的事物,同理”狗”也不必然就是现实性的东西,它跟”哈士奇”举行相比较,就是二个对峙抽象的定义。

 在代码中,”抽象”指接口、以及绝对抽象化的类,注意那里相对抽象化的类并不特指”抽象类”(使用abstract关键字注脚的类),只要二个种类在族群层次中相比靠上,那么它就足以算是抽象的,如下面举的”动物(Animal)”的例证;”具体”则指从接口、相对抽象化的类继承出来的种类,如从”动物(Animal)”继承取得的”狗(Dog)”类型。代码中架空与具体的比方见下表12-1:

表1二-1 抽象与具体举例

序号

抽象

具体

说明

1

Interface IWalkable

{

void Walk();

}

class Dog:IWalkable

{

public void Walk()

{

//…

}

}

IWalkable接口是"抽象",实现IWalkable接口的Dog类是"具体"。

2

class Dog:IWalkable

{

public void Walk()

{

//…

}

}

class HaShiQi:Dog

{

//…

}

Dog类是"抽象",继承自Dog类的HaShiQi类则是"具体"。

 

设若贰个种类涵盖1个虚幻的成员,比如”动物(Animal)”,那么那么些成员可以是很3连串型,不仅能够是”狗(Dog)”,还足以是”猫(Cat)”恐怕别的从”动物(Animal)”派生的花色,不过一旦一个项目涵盖二个针锋绝对具体的成员,比如”狗(Dog)”,那么那些成员就绝对稳定,不可再变动。很醒目,抽象的事物更易改变,”抽象”在跌落代码依赖地方起到了严重性职能。

1二.3.二 再看”倚重倒置原则”

本书前面章节在讲到”重视倒置原则”时曾建议大家在软件设计时:

一)高层模块不该一贯依赖于低层模块,高层模块和低层模块都应有借助于肤浅;

贰)抽象不应有依靠于实际,具体应该依靠于肤浅。

虚幻的事物不分明,3个品类假如含有2个接口类型成员,那么实现了该接口的拥有品种均能够成为该类型的分子,同理,方法传参也如出1辙,要是二个主意包罗2个接口类型参数,那么达成了该接口的具有类型均能够视作艺术的参数。根据”里氏替换原则(LSP)”介绍的,基类出现的地点,派生类均能够代表其出现。大家再看本章1二.2.叁小节中讲到的”信赖造成的狼狈”,DBManager类型信赖3个现实的SQLServerHelper类型,它个中含有了三个SQLServerHelper类型成员,DBManager和SQLServerHelper之间发生了1个不可变的绑定关系,假诺大家想将数据库换到MySQL数据库,要做的劳作不仅是增添3个MySQLHelper类型。假诺在软件系统规划初期,大家将访问种种数据库的相似操作提取出来,放到三个接口中,之后拜访各样现实数据库的档次均落到实处该接口,并使DBManager类型依赖于该接口:

发展历史 31

 1 //Code 12-9
 2 interface IDB //NO.1
 3 {
 4     void ExcuteSQL(string sql);
 5 }
 6 class SQLServerHelper:IDB //NO.2
 7 {
 8     //…
 9     public void ExcuteSQL(string sql)
10     {
11         //…
12     }
13 }
14 class MySQLHelper:IDB //NO.3
15 {
16     //…
17     public void ExcuteSQL(string sql)
18     {
19         //…
20     }
21 }
22 class DBManager //NO.4
23 
24 {
25     //…
26     IDB _dbHelper; //NO.5
27     public DBManager(IDB dbHelper)
28     {
29         _dbHelper = dbHelper;
30     }
31     public void Add() //NO.6
32     {
33         string sql = "";
34 
35         //…
36 
37         _dbHelper.ExcuteSQL(sql);
38 
39     }
40 
41     public void Delete() //NO.7
42     {
43         string sql = "";
44         //…
45         _dbHelper.ExcuteSQL(sql);
46     }
47     public void Update() //NO.8
48     {
49         string sql = "";
50         //…
51         _dbHelper.ExcuteSQL(sql);
52     }
53     public void Search() //NO.9
54     {
55         string sql = "";
56         //…
57         _dbHelper.ExcuteSQL(sql);
58     }
59 }

发展历史 32

如上代码Code
1二-玖所示,大家将访问数据库的情势放到了IDB接口中(NO.一处),之后有所访问其余具体数据库的品类均需兑现该接口(NO.2和NO.3处),同时DBManager类中不再包蕴具体SQLServerHelper类型引用,而是依靠于IDB接口(NO.五处),那样一来,咱们能够任由地将SQLServerHelper或许MySQLHelper类型对象作为DBManager的构造参数字传送入,甚至大家仍是可以新定义别的数据库访问类,只要该类完毕了IDB接口,

发展历史 33

 1 //Code 12-10
 2 class OracleHelper:IDB //NO.1
 3 {
 4     //…
 5     public void ExcuteSQL(string sql)
 6     {
 7         //…
 8     }
 9 }
10 class Program
11 {
12     static void Main()
13     {
14         DBManager dbManager = new DBManager(new OracleHelper()); //NO.2
15     }
16 }

发展历史 34

如上代码Code
1二-10,假如系统需求利用Oracle数据库,只需新增OracleHelper类型即可,使该品种达成IDB接口,不用修改系统此外任何代码,新增添的OracleHelper能够与已有代码合营得十二分好。

修改后的代码中,DBManager不再重视于任何3个切实品种,而是借助于3个虚无接口IDB,见下图1贰-一5:

发展历史 35

图1二-一伍 依赖于肤浅

如上航海用体育场面1贰-一5,代码修改从前,DBManager直接正视于具体品种SQLServerHelper,而代码修改后,DBManager注重于1个”抽象”,约等于说,被依赖者不鲜明是什么人,能够是SQLServerHelper,也得以是别的完毕了IDB的别的类型,DBManager与SQLServerHelper之间的依赖程度下降了。

理论上讲,任何二个类型都不应该蕴含有具体项目标分子,而只应该包涵抽象类型成员;任何3个格局都不应该包涵有实际项目参数,而只应该包罗抽象类型参数。当然这只是理论情状,软件系统规划初期就已规定不会再变更的借助关系,就不需求这么去做。

    注:除了上边谈到的将同样部分提取出来放到一个接口中,还有时候必要将同一部分提取出来,生成多少个抽象化的基类,如抽象类。接口强调平等的作为,而抽象类一般强调平等的属性,并且用在有族群层次的品类设计个中。

1二.3.3 注重注入(DI)

当八个对象之间必须存在依靠关系时,”正视倒置”为大家提供了壹种下降代码重视程度的探讨,而”重视注入(Dependency
Injection)”为大家提供了1种具体发生注重性的办法,它强调”对象间发生注重”的切切实实代码达成,是指标之间能够合营的前提。”正视注入”分以下三种(本小节代码均以12.3.贰小节中的代码为前提):

(1)构造注入(Constructor Injection);

透过构造方法,让依赖者与被注重者发生看重性关系,

发展历史 36

 1 //Code 12-11
 2 class DBManager
 3 {
 4     //…
 5     IDB _dbHelper;
 6     public DBManager(IDB dbHelper) //NO.1
 7     {
 8         _dbHelper = dbHelper;
 9     }
10     public void Add()
11     {
12         string sql = "";
13         //…
14         _dbHelper.ExcuteSQL(sql);
15     }
16     //…
17 }
18 class Program
19 {
20     static void Main()
21     {
22         DBManager manager = new DBManager(new SQLServerHelper()); //NO.2
23         DBManager manager2 = new DBManager(new MySQLHelper()); //NO.3
24         DBManager manager3 = new DBManager(new OracleHelper()); //NO.4
25     }
26 }

发展历史 37

如上代码Code
1贰-1一所示,DBManager中涵盖二个IDB类型的成员,并通过构造方法初始化该成员(NO.一处),之后方可在成立DBManager对象时分别传递不相同的数据库访问对象(NO.二、NO.叁以及NO.4处)。

经过构造方法发生的借助关系,1般在信赖者(manager、manager2以及manager3)的方方面面生命期中都有效。

    注:就算无法创建接口、抽象类的实例,不过足以存在它们的引用。

(二)方法注入(Method Injection);

由此措施,让正视者与被依赖者发生信赖关系,

发展历史 38

 1 //Code 12-12
 2 class DBManager
 3 {
 4     //…
 5     public void Add(IDB dbHelper) //NO.1
 6     {
 7         string sql = "";
 8         //…
 9         dbHelper.ExcuteSQL(sql);
10     }
11     //…
12 }
13 class Program
14 {
15     static void Main()
16     {
17         DBManager manager = new DBManager();
18         //…
19         manager.Add(new SQLServerHelper()); //NO.2
20         //…
21         manager.Add(new MySQLHelper()); //NO.3
22         //…
23         manager.Add(new OracleHelper()); //NO.4
24     }
25 }

发展历史 39

如上代码Code
1二-1贰所示,在DBManager的法门中隐含IDB类型的参数(NO.1处),大家在调用方法时,要求向它传递1些做客数据库的靶子(NO.二、NO.三以及NO.四处)。

透过措施产生的借助关系,一般在方法体内部有效。

(三)属性注入(Property Injection)。

因此性能,让正视者与被依赖者产生正视关系,

发展历史 40

 1 //Code 12-13
 2 class DBManager
 3 {
 4     //…
 5     IDB _dbHelper;
 6     public IDB DBHelper //NO.1
 7     {
 8         get
 9         {
10             return _dbHelper;
11         }
12         set
13         {
14             _dbHelper = value;
15         }
16     }
17     public void Add()
18     {
19         string sql = "";
20         //…
21         _dbHelper.ExcuteSQL(sql);
22     }
23     //…
24 }
25 class Program
26 {
27     static void Main()
28     {
29         DBManager manager = new DBManager();
30         //…
31         manager.DBHelper = new SQLServerHelper(); //NO.2
32         //…
33         manager.DBHelper = new MySQLHelper(); //NO.3
34         //…
35         manager.DBHelper = new OracleHelper(); //NO.4
36         //…
37     }
38 }

发展历史 41

如上代码Code
1二-壹三所示,DBManager中蕴藏三个公开的IDB类型属性,在须求的时候,能够设置该属性(NO.二、NO.三以及NO.四处)的值。

通过质量发生的借助关系相比灵敏,它的有效期一般介于”构造注入”和”方法注入”之间。

    注:在千千万万地方,三种正视注入的情势能够结合使用,即我们得以先经过”构造注入”让重视者与被依赖者发生正视关系,中期再使用”属性注入”的办法改变它们中间的依赖性关系。”注重注入(DI)”是以”注重倒置””为前提的。

1二.四 框架的”代码重视”

1二.四.壹 控制转换(IoC)

“控制转换(Inversion Of
Control)”强调程序运维控制权的更换,壹般形容在软件系统中,框架主导着整个程序的运转流程,如框架分明了软件系统重点的事情逻辑结构,框架使用者则在框架已有个别基础上扩大具体的政工作功效用,为此编写制定的代码均由框架在适当的机会议及展览开调用。

“控制转换”改变了我们对程序运转流程的原则性认识,程序不再受开发者控制,

发展历史 42

图1二-16 程序控制权的更换

如上海教室1二-1六所示,框架负责调用开发者编写的代码,框架控制总体程序的运营。

    注:”控制转换(IoC)、重视倒置(DIP)以及凭借注入(DI)是三个不等性质的定义,”控制转换”强调程控权的变换,珍视软件运行流程;”正视倒置”是一种降低代码注重程度的理论引导思想,它侧重软件结构;”重视注入”是目的时期时有爆发重视关系的一种具体完成情势,它爱抚编制程序达成。笔者以为部分书籍将三者做相等恐怕相似的可比是不纯粹的。

平凡,又称”控制转换(IoC)”为”好莱坞原则(霍乐迪wood
Principle)”,它提出框架与开发者编写代码之间的关系是:”Don’t call us,we
will call you.”,即全体程序的主动权在框架手中。

1二.四.二 倚重注入(DI)对框架的含义

框架与开发者编写的代码之间有”调用”与”被调用”的关联,所以制止不了注重的发出,”信赖注入”是框架与开发者编写代码之间相结合的壹种形式。任何三个框架的创小编不仅仅要遵守”重视倒置原则”,使创办出来的框架与框架使用者之间的正视性程度最小,还相应丰盛思量两者之间发生正视的主意。

    注:”框架创设者”指开发框架的集团,”框架使用者”指使用框架开发应用程序的程序员。

1二.5 本章回看

本章首先介绍了面向对象的三大特点:封装、继承和多态,它们是面向对象的根本内容。之后介绍了面向对象的软件系统开发进度中不可制止的代码正视,还波及了不客观的代码正视给大家系统开发带来的负面影响,不正常就要找出化解难点的法子,随后我们从认识”具体”和”抽象”初始,慢慢地问询能够减低代码依赖程度的具体方法,在这些进程中,”正视倒置(DIP)”是我们前进的理论教导思想,”高内聚、低耦合”是我们追求的靶子。

1二.陆 本章考虑

一.简述”面向对象”的叁大特色。

A:从目的基础、对象扩大以及对象行为四个地点来讲,”面向对象(OO)”首要涵盖3大特色,分别是:封装、继承和多态。封装是前提,它强调代码模块化,将数据以及相关的操作结合成为3个全部,对外只公开须求的走访接口;继承是在包装的前提下,创制新类型的壹种方式,它提议有族群关系的项目之间能够发生自上而下地衍生关系,处在族群底层的品种具备高层类型的有着性情;多态强调对象的有余表现作为,它是白手起家在继续的基础之上的,多态同时也是降低代码依赖程度的根本。

贰.简述”面向抽象编制程序”的有血有肉意思。

A:假如说”面向对象编制程序”教大家将代码世界中的全部东西均作为是八个完好——”对象”,那么”面向抽象编制程序”教大家将代码中持有的借助关系都建立在”抽象”之上,一切依靠均是依据抽象的,对象跟对象之间不应该有直接具体项目标引用关系。”面向接口编制程序”是”面向抽象编程”的壹种。

三.”重视倒置原则(DIP)”中的”倒置”2字作何解释?

A:经常逻辑考虑中,高层模块注重底层模块是名正言顺、理所当然的,而”正视倒置原则”提出大家拥有的高层模块不该直接重视于底层模块,而都应当借助于3个抽象,注意那里的”倒置”二字并不是”反过来”的意趣(即底层模块反过来信赖于高层模块),它只是表明不荒谬逻辑考虑中的正视顺序产生了转变,把具有违背了健康思维的事物都称为”倒置”。

四.在软件设计进度中,为了下落代码之间的注重程度,大家遵照的筹划规范是哪些?我们设计的指标是哪些?

A:有两大统一筹划规范重尽管为着下降代码依赖程度,即:单一职责规范(S大切诺基P)和正视性倒置原则(DIP)。我们在软件设计时追求的靶子是:高内聚、低耦合。

作者:周见智 
出处:http://www.cnblogs.com/xiaozhi\_5638/ 
本文版权归小编和新浪共有,欢迎转发,但未经小编同意必须保留此段注解,且在文章页面鲜明地方给出原著连接,不然保留追究法律义务的任务。

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注