难免的窘迫

 

 

系作品连接

重力之源:代码中的泵

大观:梳理编程约定

编程的基础:数据类型(一)

编程的基础:数据类型(二)

而复用代码:组件的全过程

重中之重:委托以及事件

东西以类聚:对象啊发生生命

难免的难堪:代码依赖

  • 12.1 从面向对象先导
    • 12.1.1 对象基础:封装
    • 12.1.2 对象扩张:继承
    • 12.1.3 对象行为:多态
  • 12.2 不可制止的代码依赖
    • 12.2.1 依赖是的原由
    • 12.2.2 耦合与内聚
    • 12.2.3 倚重造成的难堪
  • 12.3 降低代码看重
    • 12.3.1 认识抽象和现实
    • 12.3.2 再看“依赖倒置原则”
    • 12.3.3 依赖注入
  • 12.4 框架中的“代码依赖”
    • 12.4.1 控制转换
    • 12.4.2 依赖注入对框架的义
  • 12.5 本章回顾
  • 12.6 本章思考

每当浩渺的代码世界被,有着广大之目的,跟人和人口之间时有暴发集体及关系一样,对象及对象期间吧制止不了点,所谓接触,就是指一个靶要动及另外对象的性能、方法等成员。现实生活中一个总人口之张罗关系复杂或者连无是啊不佳的事情,可是对于代码中的靶子而言,复杂的”社交关系”往往是不提倡的,因为对象之间的关联性越怪,意味着代码改动一处在,影响之范围就谋面更老,而当时统统不便利系统重构和晚维护。所以于当代软件开发过程中,我们相应按”尽量降低代码倚重”的法,所谓尽量,就曾经注解代码倚重不可避免。

偶然一味地追”降低代码依赖”反而会使系统更扑朔迷离,我们要在”降低代码依赖”和”扩大系统规划复杂”之间找到一个平衡点,而无该去盲目追求”几人数定理”这种设计境界。

    注:”六总人口定理”指:任何六只人口以内的涉及带来,基本确定于两个人左右。两单陌生人之间,可以经三只人来建立联系,此为六丁定律,也如作六总人口法则。

12.1 从面向对象开头

于处理器科技提升历史遭,编程的法直接如故趋向于简单化、人性化,”面向对象编程”正是历史发展有平等等的名堂,它的面世不仅是为增强软件开发的频率,还切人们对代码世界与忠实世界的联认识观。当说到”面向对象”,出现于大家脑海中的词无非是:类,抽闲,封装,继承与多态,本节将从指标基础、对象扩充及对象行为多少个点针对”面向对象”做出解释。

    注:面向对象中之”面向”二字意指:在代码世界被,大家该将此外事物还扣留做成一个封闭的单元,这些单元就是”对象”。对象不仅能够表示一个足看得见摸得在的体,它还足以代表一个空洞过程,从理论及说话,任何实际的、抽象的物都好定义成一个靶。

12.1.1 对象基础:封装

暨实际世界一样,无论由微观上或者宏观上看,这么些世界都是由于许许多多的么独立物体组成,小到人口、器官、细胞,大及国、星球、宇宙,
每个独立单元都起友好之习性与行。仿照现实世界,大家拿代码中起关联性的数码以及操作合并起来形成一个圆,之后于代码中数与操作均是盖一个完好无损出现,这么些过程叫”封装”。封装是面向对象的底子,有了打包,才会生出完的概念。

图片 1

祈求12-1 封装前后

倘达到图12-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
12-1拿生是人群定义成了一个Student类(NO.1处),它含有三单字段:分别吗保留姓名的_name、保存年龄的_age以及保存好的_hobby字段,这三只字段都是私家访问权限,为了有利于以外看中的数码,又分别定义了三单特性:分别吗看姓名的Name,注意该属性是不过读之,因为健康状态下姓名无法重新被外改变;访问年龄的Age,注意当让年龄赋值小于等于0时,代码自动将该安装也1;访问爱好的Hobby,外界可以由此该属性对_hobby字段举办了看。同时Student类包含六只形式,一个公然的SyaHello()方法及一个于保障之GetSayHelloWords()方法,前者肩负输出对象好的”介绍音讯”,后者负责格式化”介绍音讯”的字符串。Student类图见图12-2:

图片 4

图12-2 Student类图

    注:上文大校类的积极分子访问权限只分为两独片,一个针对性外场可见,包括public;另一样栽对外面不可见,包括private、protected等。

只顾类和对象的区分,假使说对象是代码世界对切实世界面临各样东西的顺序映射,那么看似就是这几个映射的沙盘,通过沙盘创设具体的炫耀实例:

图片 5

贪图12-3 对象实例化

咱得以看到代码Code
12-1惨遭之Student类既涵盖个人成员为饱含公开成员,私有成员对外场不可见,外界如得访问对象,只可以调用给起之公开情势。这样做的目标就是拿以外不必要打听之新闻隐藏起来,对外只供简单的、易懂的、稳定之公开接口即可方便以外对拖欠档的利用,同时为防止了外围对目标中数据不必要的修改及走访所招的死去活来。

打包的守则:

包裹是面向对象的率先步,有了打包,才会晤暴发相近、对象,再才会讲继承、多态等。经过前人丰硕的尽及小结,对封装有以下规则,我们以平日其实支付被应该尽量遵守这些轨道:

1)一个品种应该尽可能少地爆出自己之里边音信,将细节的有的隐藏起来,只对曾外祖父开必要之普洱久安的接口;同理,一个列应该尽可能少地问询任何品类,这虽是平日说的”迪米特法则(Law
of
Demeter)”,迪米特法则又让称呼”最小知识标准化”,它强调一个档次应该尽可能少地解其他系列的里贯彻,它是降低代码依赖之一个重大带领思想,详见本章后续介绍;

2)理论及,一个色的内部代码可以轻易更改,而未应影响对外公开的接口。这就是要求大家用”善变”的一些隐藏到花色中,对曾祖父开之终将是争持稳定性的;

3)封装并无单指代码层面上,如类型中之字段、属性和艺术等,更多之时光,我们可以以这个以到系统结构层面达到,一个模块乃至系统,也应当就对外提供稳定的、易用的接口,而以现实实现细节隐藏在系内。

包的意思:

包裹不仅会有利于对代码对数码的统一管理,它还有以下意义:

1)封装隐藏了型的求实贯彻细节,保证了代码安全性与泰;

2)封装对外围就供稳定之、易用的接口,外部使用者不欲过多地问询代码实现原理也不需要控制复杂难以了然的调用逻辑,就能挺好地使项目;

3)封装保证了代码模块化,提升了代码复用率并保证了网功用的分离。

12.1.2 对象扩张:继承

包强调代码合并,封装的结果就是创设一个个单独的包件:类。那么我们发没暴发外的道去创设新的包装件为?

在现实生活中,一种物体往往衍生自其余一栽物体,所谓衍生,是凭衍生体在具备为衍生体的性质基础及,还装有任何额外的风味,被衍生体往往再抽象,而衍生体则再一次具体,如大学衍生自高校,因为大学有高校的特征,但高校以于学实际,人衍生自生物,因为人口有生物之特色,但人口同时相比较生物具体。

图片 6

图12-4 高校衍生图

而达到图12-4,高校相对来讲太抽象,大学、高中与小学都可以衍生自高校,进一步来拘禁,大学其实也比空虚,因为大学还足以有切实的本科、专科,由此本科及专科学校可以衍生自高校,当然,抽象和求实的概念是对峙的,假如你看本科还不够具体,那么她可重衍生出来一听从、二听从及三本。

于代码世界面临,也设有”衍生”这同游说,从一个比肤浅的档次衍生出一个相比具体的系列,我们遂”后者派生自前者”,假若A类型派生自B类型,那么称这进程为”继承”,A称之为”派生类”,B则称为”基类”。

    注:派生类又受形象地叫做”子类”,基类又被形象地叫”父类”。

每当代码12-1碰着的Student类基础及,虽然大家要创设一个大学生(College_Student)的类别,那么我们了可于Student类派生出一个初的高等高校生类,因为大学生有学生的特色,但以于生再一次切实:

图片 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
12-2所著,College_Student类继承Student类(NO.1处),College_Student类具备Student类的性质,比如Name、Age以及Hobby,同时College_Student类还增了额外的业内(Major)属性,通过以派生类吃还写GetSyaHelloWords()方法,大家再度格式化”个人消息”字符串,让该蕴含”专业”的音信(NO.3地处),最终,调用College_Student中于基类继承下去的SayHello()方法,便可以轻松输出自己之个人消息。

俺们看,派生类通过连续取得了基类的方方面面音,之外,派生类还是可以够扩充新的始末(如College_Student类中新增的Major属性),基类到派生类是一个空洞到实际的进程,由此,我们于规划类的上,通常拿通用部分提取出,形成一个基类,未来所有与基类有种族关系的色均可以继续该基类,以基类为底蕴,扩展和谐故意的性质。

图片 9

图12-5 College_Student类继承图

一对上,一种档次只用于其他连串派生,从来不需要创建它的有具体对象实例,这样的类低度抽象化,我们遂这体系似为”抽象类”,抽象类不负担创制具体的目的实例,它含了派生类型的协同成分。除了通过持续某个项目来创设新的型,.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
12-3所出示,People和Dog类型均贯彻了IWalkable接口,那么它必须还实现IWalkable接口中之Walk()方法,见下图12-6:

图片 12

图12-6 接口继承

连续包括个别栽格局,一种植呢”类继承”,一种乎”接口继承”,它们的来意类似,都是于存活项目基础及创建有新的项目,不过它们为有分别:

1)类继承强调了族群关系,而接口继承强调通用功效。类继承中之基类和派生类属于祖宗和后人的关系,而接口继承中之接口及贯彻了接口的类别并不曾这种关系。

2)类继承强调”我是(Is-A)”的干,派生类”是”基类(注意这里的”是”代表派生类具有基类的特点),而接口继承强调”我能够召开(Can-Do)”的关联,实现了接口的类有接口中规定之行为能力(由此接口在命名时都为”able”作为后缀)。

3)类继承中,基类即使比肤浅,可是她可以来现实的贯彻,比如方法、属性之落实,而接口继承中,接口不允许暴发其他的具体贯彻。

承的准则:

持续是面向对象编程中创立项目标均等种方法,在卷入的基础及,它能抽工作量、提升代码复用率的以,快捷地创造有富有相似性的类型。在使持续时,请按照以下规则:

1)严俊遵循”里氏替换原则”,即基类出现的地点,派生类一定好出现,因而,不要盲目地去下持续,假若个别独八九不离十没有衍生的涉嫌,那么就未该生出延续关系。如若被猫(Cat)类派生自狗(Dog)类,那么坏爱就可以看到,狗类出现的地点,猫类不必然可以代替它现身,因为它两素不怕从未抽象和现实性的层系关系。

2)由于派生类会继承基类的全体内容,所以只要严俊控制好路的接续层次,不然派生类的体积会越来越大。此外,基类的改动得会潜移默化及派生类,继承层次太多科学管理,继承是长耦合的极致关键因素。

3)继承强调类型中的特性,而无特性。因而我们一般以品种且具备的局部提取出,形成一个基类(抽象类)或者接口。

12.1.3 对象行为:多态

“多态”一词来源于生物学,本意是恃地球上之富有生物展现出形象和状态的多样性。在面向对象编程中多态是据:同一操作效用被无同类的实例,将出不同的尽结果,即非同类的目的吸收一模一样的音讯不时,拿到不同之结果。

多态强调面向对象编程中,对象的有余见作为,见下代码Code 12-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-4所出示,分别定义了五个近乎:Student(NO.1处)、College_Student(NO.2处)、Senior_HighSchool_Student(NO.3介乎),前面两单类似继承自Student类,视同一律复写了SayHello()方法。在客户端代码中,对于与一行代码”student.IntroduceMyself();”而言,三坏调用(NO.4、NO.5以及NO.6处在),屏幕输出的结果也不一样:

图片 15

祈求12-7 多态效果

假如齐图12-7所体现,三不良调动用和一个智,不同目的来例外之显现作为,我们誉为”对象的多态性”。从代码Code
12-4遭受得以望,之所以出现雷同的调用会发不同之变现作为,是盖被基类引用student赋值了不同之派生类对象,并且派生类吃更写了SayHello()虚方法。

对象的多态性是为”继承”为前提的,而延续又分为”类继承”和”接口继承”两近乎,那么多态性也发一定量栽形式:

1)类继承式多态;

类继承式多态需要虚方法的涉企,正使代码Code
12-4际遇这样,派生类在必要通常,必须重写基类的虚方法,最终使基类引用调用各种叫生类对象的方法,达到多表现作为的效果:

2)接口继承式多态。

接口继承式多态不欲虚方法的出席,在代码Code 12-3之底子及编制如下代码:

图片 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
12-5所显示,对于和一行代码”iw.Walk();”的简单不行调用(NO.1和NO.2地处),有例外之变现作为:

图片 18

希冀12-8 接口继承式多态

在面向对象编程中,多态的前提是延续,而连续的前提是包裹,三者缺一不可。多态也是凡退代码看重的有力保障,详见本章后续有关内容。

12.2 不可防止的代码依赖

本书后面章节曾介绍了,程序的举办过程就是是办法的调用过程,有办法调用,必然会促使对象及对象之间时有暴发依赖性,除非一个对象不插足程序的运行,这样的目标就像相同所孤岛,与此外对象没此外交互,不过这么的目的呢就没有任何有价值。因而,在我们的程序代码中,任何一个目的自然会跟其它一个甚至还多单目的来倚重性关系。

12.2.1 依赖是的缘由

“方法调用”是极致常见有倚重性之因,一个对象和此外对象自然会通信(除非我们拿具有的代码逻辑全部描写在了是目标中),通信平时状态下虽然代表有措施的调用,有方法的调用就象征这简单只目标期间有依靠关系(至少要爆发此外对象的援才能调用方法),此外常见的均等种植有看重的原因是:继承,没错,继承就算为我们带了杀特别之利,却为深受我们带了代码依赖。依赖发生的原由大约可以分以下四类:

1)继承;

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

图片 19

希冀12-9 继承依赖

齐图12-9备受,B和C继承自A,A类改变必将会影响B和C的转移。

2)成员对象;

一个色涵盖此外一个种的积极分牛时,前者必然受控于后人,尽管后者的更改不肯定会影响及前端:

图片 20

祈求12-10 成员对象看重

万一齐图12-10,A包含B类型的积极分子,那么A就让控于B,B在A内部完全可见。

    注:成员对象倚重和组合(聚合)类似。

3)传递参数;

一个列作为参数传递给此外一个路的积极分子方法,那么后者肯定会让控于前者,尽管前者的改不肯定会影响及后者:

图片 21

贪图12-11 传参依赖

假诺齐图12-11,A类型的法门Method()包含一个B类型的参数,那么A就吃控于B,B在A的Method()方法可见。

4)临时变量。

其余时刻,一个品类将其余一个品类用作了临时变量时,那么前者就受控于后者,尽管后者的转不必然会潜移默化至前者:

图片 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
12-6,B的DoSomething()方法吃利用了A类型的旋对象,A在B的DoSomething()方法中一些范围可见。

常备情形下,通过让倚重者在依赖者内部可见范围大小来衡量依赖程度的高低,原因非凡粗略,可见范围越怪,表达访问它的概率就一发老,看重者受影响的票房价值为即愈加怪,因而,上述四种植据暴发的原由中,倚重程度按梯次依次降低。

12.2.2 耦合与内聚

为衡量对象中因程度的轻重,大家推荐了”耦合”这等同定义,耦合度越强,表达对象之间的仗程度进一步强;为了衡量对象独立性的高低,我们推荐了”内聚”这同一概念,内聚性越强,表明对象同外面交互越少、独立性越强。很肯定,耦合与内聚是鲜单互相对峙又细致入微相关的概念。

    注:从广义上说话,”耦合”与”内聚”不仅可对象同目的期间的涉,也称模块和模块、系统以及网内的涉及,那和后面说”封装”时强调”封装”不仅仅指代码层面达到的道理同样。

“模块功效集中,模块之间界限显著”一贯是软件设计追求的对象,软件系统非会晤为急需的改观、功用的升官要只好大范围修改原来就部分源代码,换句话说,我们以软件设计中,应该严苛坚守”高内聚、低耦合”的极。下图12-12体现一个系统按照该法上下:

图片 24

图12-12 高内聚、低耦合

设若齐图12-12所呈现,”高内聚、低耦合”强调对象及目的中(模块和模块之间)尽可能多地下降因程度,每个对象(或模块,下同)尽可能提高自己之独立性,这即要求其分别承担之效率相对集中,代码结构由”开放”转向”收敛”。

“职责单一原则(SRP)”是增长对象内聚性的理论指点思想有,它指出每个对象就担负某一个(一近乎)功用。

12.2.3 依赖造成的”尴尬”

设若当软件系统规划初期,没有成立地回落(甚至避免)代码间的耦合,系统出中期往往会碰到中期不可预期的不便。下边举例表明依赖让我们造成的”窘迫”。

而一个快要开发之系被运用到了数据库,系统设计阶段确定下SQL
Server数据库,依照”代码模块化能够提高代码复用性”的尺度,我们拿造访SQL
Server数据库的代码封装成了一个独立的类似,该类只负责访问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 12-7所著,定义了一个SQL
Server数据库访问类SQLServerHelper(NO.1处),该类专门负责访问SQL
Server数据库,如举办sql语句(此外成效略),然后定义了一个数据库管理类DBManager(NO.2处),该类负责一些数的增删改查(NO.4、NO.5、NO.6以及NO.7处在),同时此类还富含一个SQLServerHelper类型成员(NO.3地处),负责具体SQL
Server数据库的造访。SQLServerHelper类和DBManager类的干显示下图12-13:

 

图片 27

贪图12-13 看重让现实

设若齐图12-13所著,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
12-8,定义了一个专门访问MySQL数据库的类型MySQLHelper,它的协会及SQLServerHelper相同,接下,为了使原先早就工作正常的系统还适应被MySQL数据库,大家尚得逐一修改DBManager类中具有对SQLServerHelper类型的援,将这些全部立异也MySQLHelper的援。如若只是是一个DBManager类使用到了SQLServerHelper的话,整个更新工作量还未算是大多,但即使程序代码中还起其他地点用及了SQLServerHelper类型的话,这多少个工作量虽大量,除此之外,我们如此做出的所有操作了背离了软件设计中的”开闭原则(OCP)”,即”对扩大开放,而针对性修改关闭”。很通晓,大家以增添新的花色MySQLHelper时,还修改了网本来代码。

出现上述所说问题的根本因是,在网规划初期,DBManager那一个项目因了一个实际项目SQLServerHelper,”具体”就意味着不可变更,同时为便认证两单门类中的负关系曾经抵达了”非你不可”的水准。要缓解上述问题,需要我们在软件设计初期就做出肯定之主意,详见下一样不怎么节。

12.3 降低代码依赖

上一节末尾说到了代码依赖让大家办事牵动的艰苦,还关系了紧要缘由是目的及对象中(模块和模块,下同)看重关系最过紧密,本节紧要表明如何去降低代码间的依赖程度。

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

实在本书在此以前多地点已面世了”具体”和”抽象”的词眼,如”具体的品类”、”依赖让肤浅而不现实”等等,到近来停止,本书还连无系统地介绍这二者的有血有肉意思。

所谓”抽象”,即”不显、未知、可改”的意,而”具体”则是相反的意义,它表示”确定、不可变更”。我们于头里说”继承”时即说过,派生类继承自基类,就是一个”抽象到现实”的历程,比如基类”动物(Animal)”就是一个架空的事物,而起基类”动物(Animal)”派生出的”狗(Dog)”就是一个具体的事物。抽象和具体的干使下图12-14:

图片 30

希冀12-14 抽象和具体的相对性

    注:抽象和具象为是一个针锋相对的概念,并无可以说”动物”就决然是一个架空的东西,它跟”生物”举办比,就是一个相对具体的物,同理”狗”也无肯定就是是切实的事物,它跟”哈士奇”举办相比较,就是一个针锋相对抽象的概念。

 在代码中,”抽象”指接口、以及相对抽象化的接近,注意那里相对抽象化的类似并无特指”抽象类”(使用abstract关键字表明的好像),只要一个项目在族群层次中较靠上,那么它们就得算抽象的,如达到面举的”动物(Animal)”的事例;”具体”则指从接口、相对抽象化的类继承出来的种,如打”动物(Animal)”继承取得的”狗(Dog)”类型。代码中泛和具体的比方见下表12-1:

表达12-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类则是"具体"。

 

尽管一个类涵盖一个空洞的分子,比如”动物(Animal)”,那么是成员可以是充足多连串型,不仅可是”狗(Dog)”,还好是”猫(Cat)”或者另外由”动物(Animal)”派生的体系,不过假使一个序列涵盖一个相持具体的积极分子,比如”狗(Dog)”,那么这多少个成员就是相对稳定,不可再转移。很明朗,抽象的东西更易改变,”抽象”在减低代码倚重地点于及了重大功效。

12.3.2 再拘留”倚重倒置原则”

本书前边章节在讲到”看重倒置原则”时既提出我们于软件设计时:

1)高层模块不应直接倚重让低层模块,高层模块和低层模块都应当因让肤浅;

2)抽象不该负让现实,具体该借助让肤浅。

虚幻的物不确定,一个项目倘诺含有一个接口类型成员,那么实现了该接口的备类型均好变成该品种的积极分子,同理,方法传参也同等,要是一个措施包含一个接口类型参数,那么实现了该接口的兼具品类均雅观做艺术的参数。按照”里氏替换原则(LSP)”介绍的,基类现身的地点,派生类都可以代表其现出。我们还拘留本章12.2.3小节中提到的”依赖造成的窘迫”,DBManager类型依赖一个现实的SQLServerHelper类型,它其中含有了一个SQLServerHelper类型成员,DBManager和SQLServerHelper之间暴发了一个不可变的绑定关系,假若我们牵记拿数据库换成MySQL数据库,要做的工作不仅是加一个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
12-9所著,大家将做客数据库的道放到了IDB接口中(NO.1处),之后所有访问其它具体数据库的系列均用兑现该接口(NO.2和NO.3地处),同时DBManager类中不再包含具体SQLServerHelper类型引用,而是借助让IDB接口(NO.5介乎),这样一来,我们可以凭地拿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
12-10,如若系统要采取Oracle数据库,只待新增OracleHelper类型即可,使该品种实现IDB接口,不用修改系统外任何代码,新加的OracleHelper可以跟曾发代码合作得大好。

改后底代码中,DBManager不再倚重让其他一个有血有肉品种,而是依靠让一个虚幻接口IDB,见下图12-15:

图片 35

祈求12-15 倚重让肤浅

要是达到图12-15,代码修改前,DBManager直接倚重让具体项目SQLServerHelper,而代码修改后,DBManager看重于一个”抽象”,也就是说,被依赖者不确定是哪位,可以是SQLServerHelper,也可以是其它实现了IDB的任何项目,DBManager与SQLServerHelper之间的借助程度降低了。

答辩及称,任何一个档且未该包含有具体项目标分子,而唯有当包含抽象类型成员;任何一个智还无应该包含有现实品种参数,而只应包含抽象类型参数。当然这仅是辩论情形,软件系统规划初期就既规定无会面再一次转之依赖性关系,就不需要这样去开。

    注:除了下边说到之将同样部分提取出来放到一个接口中,还有上用以一律部分提取出来,生成一个抽象化的基类,如抽象类。接口强调平等的行,而肤浅类一般强调平等的性质,并且因而当有族居多层次之档次设计中。

12.3.3 看重注入(DI)

当半只目的期间必须有依靠关系时,”看重倒置”为咱提供了相同种植下跌代码倚重程度之牵挂,而”依赖注入(Dependency
Injection)”为我们提供了同样种植具体有倚重性之点子,它强调”对象中发生依赖”的切实代码实现,是目的之间会面作的前提。”倚重注入”分以下两种(本小节代码都为12.3.2小节中的代码为前提):

(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
12-11所著,DBManager中富含一个IDB类型的分子,并由此构造方法初步化该成员(NO.1处),之后方可在开创DBManager对象时分别传递不同之数据库访问对象(NO.2、NO.3以及NO.4处)。

通过构造方法爆发的负关系,一般在依赖者(manager、manager2以及manager3)的全方位生命期中都使得。

    注:即使未可以创立接口、抽象类的实例,然而足以是它的援。

(2)方法注入(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
12-12所出示,在DBManager的计被包含IDB类型的参数(NO.1处),我们于调用方法时,需要向其传递一些拜数据库的目的(NO.2、NO.3以及NO.4处)。

通过艺术爆发的依靠关系,一般在方法体内部有效。

(3)属性注入(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
12-13所出示,DBManager中蕴含一个精通的IDB类型属性,在必要之上,可以设置该属性(NO.2、NO.3以及NO.4处)的值。

经过性暴发的依关系比灵敏,它的有效期一般在”构造注入”和”方法注入”之间。

    注:在成千上万场所,二种植据注入的计得以做以,即我们得以先通过”构造注入”让倚重者与被依赖者爆发看重性关系,先前时期还下”属性注入”的格局改变它们中的靠关系。”依赖注入(DI)”是为”依赖倒置””为前提的。

12.4 框架的”代码依赖”

12.4.1 控制转换(IoC)

“控制转换(Inversion Of
Control)”强调程序运行控制权的变,一般形容在软件系统被,框架主导着整个程序的运转流程,如框架确定了软件系统要的作业逻辑结构,框架使用者则当框架已经部分基础及扩展具体的事务效率,为是编制的代码都由框架在当的机遇举办调用。

“控制转换”改变了咱本着程序运行流程的一定认识,程序不再吃开发者控制,

图片 42

祈求12-16 程序控制权的转移

假诺齐图12-16所展现,框架负责调用开发者编写的代码,框架控制总体程序的运作。

    注:”控制转换(IoC)、依赖倒置(DIP)以及借助注入(DI)是两只例外属性的定义,”控制转换”强调程序控制权的转移,注重软件运行流程;”看重倒置”是相同栽下跌代码看重程度之理论指点思想,它侧重软件结构;”依赖注入”是目的之间时有发生依赖性关系之一样种植具体实现格局,它重编程实现。笔者以为有些书以三者做顶或者相似的较是不准确之。

平时,又如”控制转换(IoC)”为”好莱坞原则(Hollywood
Principle)”,它提出框架和开发者编写代码之间的干是:”Don’t call us,we
will call you.”,即满程序的主动权在框架手中。

12.4.2 倚重注入(DI)对框架的意义

框架和开发者编写的代码之间来”调用”与”被调用”的干,所以避免不了负之来,”倚重注入”是框架和开发者编写代码之间互相结合的同栽艺术。任何一个框架的奠基人不仅仅要论”倚重倒置原则”,使创办出来的框架和框架使用者之间的仗程度最小,还应有丰裕考虑两者之间发生依赖性之法子。

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

12.5 本章回顾

本章首先介绍了面向对象的老三雅特征:封装、继承与多态,它们是面向对象的要紧内容。之后介绍了面向对象的软件系统出过程被不可防止的代码倚重,还涉嫌了非创立之代码依赖让我们系开发带来的负面影响,有问题且找有解决问题的方,随后我们由认识”具体”和”抽象”开端,逐步地问询得落代码依赖程度之具体方法,在这历程遭到,”倚重倒置(DIP)”是我们发展的理论指导思想,”高内聚、低耦合”是我们追求的靶子。

12.6 本章思考

1.简述”面向对象”的老三可怜特征。

A:从目的基础、对象扩展以及对象行为三单方面来讲,”面向对象(OO)”紧要含有三雅特点,分别是:封装、继承和多态。封装是前提,它强调代码模块化,将数据和相关的操作结合成一个整,对外只公开必要之拜会接口;继承是当包装的前提下,制造新路的相同栽方法,它提议有族群关系的序列中可出自上而下地衍生关系,处在族群底层的类型有高层类型的具有特性;多态强调对象的有余表现作为,它是立将来续的底蕴之上的,多态同时也是降低代码倚重程度的要害。

2.简述”面向抽象编程”的切实意思。

A:倘诺说”面向对象编程”教大家拿代码世界面临之享有东西都作是一个完全——”对象”,那么”面向抽象编程”教我们用代码中颇具的凭关系都立于”抽象”之上,一切依靠均是遵照抽象的,对象和对象中未应当出直接具体品种的援关系。”面向接口编程”是”面向抽象编程”的同种。

3.”倚重倒置原则(DIP)”中的”倒置”二字作何解释?

A:正常逻辑思考中,高层模块倚重底层模块是名正言顺、理所当然的,而”倚重倒置原则”提议我们有的高层模块不应一向倚重让底层模块,而还当负让一个虚无,注意这里的”倒置”二许连无是”反过来”的意(即底层模块反过来倚重让高层模块),它只是表明正常逻辑思考中之凭顺序来了变化,把装有违背了健康思维的事物还叫作”倒置”。

4.于软件设计过程中,为了降低代码之间的靠程度,我们本的规划条件是啊?我们设计之靶子是呀?

A:有星星点点百般计划原则重即便为降低代码依赖程度,即:单一任务规范(SRP)和仰倒置原则(DIP)。大家以软件设计时追求的目的是:高内聚、低耦合。

作者:周见智 
出处:http://www.cnblogs.com/xiaozhi\_5638/ 
本文版权归作者和果壳网共有,欢迎转载,但未经作者同意要保留这一个段子阐明,且以篇章页面显然地点让闹原文连接,否则保留追究法律责任的权。

 

 

 

发表评论

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