在浩瀚的代码世界中有着无数的对象跟人和人之间有社交关系一样对象跟对象之间也避免不了接触所谓接触就是指一个对象要使用到另外对象的属性、方法等成员。现实生活中一个人的社交关系复杂可能并不是什么不好的事情然而对于代码中的对象而言复杂的社交关系往往是不提倡的因为对象之间的关联性越大意味着代码改动一处影响的范围就会越大而这完全不利于系统重构和后期维护。所以在现代软件开发过程中我们应该遵循尽量降低代码依赖的原则所谓尽量就已经说明代码依赖不可避免。有时候一味地追求降低代码依赖反而会使系统更加复杂我们必须在降低代码依赖和增加系统设计复杂性之间找到一个平衡点而不应该去盲目追求六人定理那种设计境界。注六人定理指任何两个人之间的关系带基本确定在六个人左右。两个陌生人之间可以通过六个人来建立联系此为六人定律也称作六人法则。12.1 从面向对象开始在计算机科技发展历史中编程的方式一直都是趋向于简单化、人性化面向对象编程正是历史发展某一阶段的产物它的出现不仅是为了提高软件开发的效率还符合人们对代码世界和真实世界的统一认识观。当说到面向对象出现在我们脑海中的词无非是类抽闲封装继承以及多态本节将从对象基础、对象扩展以及对象行为三个方面对面向对象做出解释。注面向对象中的面向二字意指在代码世界中我们应该将任何东西都看做成一个封闭的单元这个单元就是对象。对象不仅仅可以代表一个可以看得见摸得着的物体它还可以代表一个抽象过程从理论上讲任何具体的、抽象的事物都可以定义成一个对象。12.1.1 对象基础封装和现实世界一样无论从微观上还是宏观上看这个世界均是由许许多多的单个独立物体组成小到人、器官、细胞大到国家、星球、宇宙 每个独立单元都有自己的属性和行为。仿照现实世界我们将代码中有关联性的数据与操作合并起来形成一个整体之后在代码中数据和操作均是以一个整体出现这个过程称为封装。封装是面向对象的基础有了封装才会有整体的概念。图12-1 封装前后如上图12-1所示图中左边部分为封装之前数据和操作数据的方法没有相互对应关系方法可以访问到任何一个数据每个数据没有访问限制显得杂乱无章图中右边部分为封装之后数据与之关联的方法形成了一个整体单元我们称为对象对象中的方法操作同一对象的数据数据之间有了保护边界。外界可以通过对象暴露在外的接口访问对象比如给它发送消息。通常情况下用于保存对象数据的有字段和属性字段一般设为私有访问权限只准对象内部的方法访问而属性一般设为公开访问权限供外界访问。方法就是对象的表现行为分为私有访问权限和公开访问权限两类前者只准对象内部访问而后者允许外界访问。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(value0) 23 { 24 value1; 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 }上面代码Code 12-1将学生这个人群定义成了一个Student类NO.1处它包含三个字段分别为保存姓名的_name、保存年龄的_age以及保存爱好的_hobby字段这三个字段都是私有访问权限为了方便外界访问内部的数据又分别定义了三个属性分别为访问姓名的Name注意该属性是只读的因为正常情况下姓名不能再被外界改变访问年龄的Age注意当给年龄赋值小于等于0时代码自动将其设置为1访问爱好的Hobby外界可以通过该属性对_hobby字段进行完全访问。同时Student类包含两个方法一个公开的SyaHello()方法和一个受保护的GetSayHelloWords()方法前者负责输出对象自己的介绍信息后者负责格式化介绍信息的字符串。Student类图见图12-2图12-2 Student类图注上文中将类的成员访问权限只分为两个部分一个对外界可见包括public另一种对外界不可见包括private、protected等。注意类与对象的区别如果说对象是代码世界对现实世界中各种事物的一一映射那么类就是这些映射的模板通过模板创建具体的映射实例图12-3 对象实例化我们可以看到代码Code 12-1中的Student类既包含私有成员也包含公开成员私有成员对外界不可见外界如需访问对象只能调用给出的公开方法。这样做的目的就是将外界不必要了解的信息隐藏起来对外只提供简单的、易懂的、稳定的公开接口即可方便外界对该类型的使用同时也避免了外界对对象内部数据不必要的修改和访问所造成的异常。封装的准则封装是面向对象的第一步有了封装才会有类、对象再才能谈继承、多态等。经过前人丰富的实践和总结对封装有以下准则我们在平时实际开发中应该尽量遵循这些准则1一个类型应该尽可能少地暴露自己的内部信息将细节的部分隐藏起来只对外公开必要的稳定的接口同理一个类型应该尽可能少地了解其它类型这就是常说的迪米特法则Law of Demeter迪米特法则又被称作最小知识原则它强调一个类型应该尽可能少地知道其它类型的内部实现它是降低代码依赖的一个重要指导思想详见本章后续介绍2理论上一个类型的内部代码可以任意改变而不应该影响对外公开的接口。这就要求我们将善变的部分隐藏到类型内部对外公开的一定是相对稳定的3封装并不单指代码层面上如类型中的字段、属性以及方法等更多的时候我们可以将其应用到系统结构层面上一个模块乃至系统也应该只对外提供稳定的、易用的接口而将具体实现细节隐藏在系统内部。封装的意义封装不仅能够方便对代码对数据的统一管理它还有以下意义1封装隐藏了类型的具体实现细节保证了代码安全性和稳定性2封装对外界只提供稳定的、易用的接口外部使用者不需要过多地了解代码实现原理也不需要掌握复杂难懂的调用逻辑就能够很好地使用类型3封装保证了代码模块化提高了代码复用率并确保了系统功能的分离。12.1.2 对象扩展继承封装强调代码合并封装的结果就是创建一个个独立的包装件类。那么我们有没有其它的方法去创建新的包装件呢在现实生活中一种物体往往衍生自另外一种物体所谓衍生是指衍生体在具备被衍生体的属性基础上还具备其它额外的特性被衍生体往往更抽象而衍生体则更具体如大学衍生自学校因为大学具备学校的特点但大学又比学校具体人衍生自生物因为人具备生物的特点但人又比生物具体。图12-4 学校衍生图如上图12-4学校相对来讲最抽象大学、高中以及小学均可以衍生自学校进一步来看大学其实也比较抽象因为大学还可以有具体的本科、专科因此本科和专科可以衍生自大学当然抽象和具体的概念是相对的如果你觉得本科还不够具体那么它可以再衍生出来一本、二本以及三本。在代码世界中也存在衍生这一说从一个较抽象的类型衍生出一个较具体的类型我们称后者派生自前者如果A类型派生自B类型那么称这个过程为继承A称之为派生类B则称之为基类。注派生类又被形象地称为子类基类又被形象地称为父类。在代码12-1中的Student类基础上如果我们需要创建一个大学生College_Student的类型那么我们完全可以从Student类派生出一个新的大学生类因为大学生具备学生的特点但又比学生更具体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 }如上代码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属性基类到派生类是一个抽象到具体的过程因此我们在设计类型的时候经常将通用部分提取出来形成一个基类以后所有与基类有种族关系的类型均可以继承该基类以基类为基础增加自己特有的属性。图12-5 College_Student类继承图有的时候一种类型只用于其它类型派生从来不需要创建它的某个具体对象实例这样的类高度抽象化我们称这种类为抽象类抽象类不负责创建具体的对象实例它包含了派生类型的共同成分。除了通过继承某个类型来创建新的类型.NET中还提供另外一种类似的创建新类型的方式接口实现。接口定义了一组方法所有实现了该接口的类型必须实现接口中所有的方法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 //…