从这一章开始,基本就脱离了结构化编程的思维,所以我的博客排版方式也会摒弃之前的节选方式,而是深入的去理解和描述内容,而这本书给我带来的惊喜就是:能够从设计的角度去帮助我理解为何类要如此设计,其设计缘由是什么,我觉得这一点远比单纯的告诉你怎么用更重要!感觉这一章的干货特别多,可以让我的面向对象思维上一个小小的台阶,这里将自己的学习笔记以及自己的理解整理分享出来,希望对大家都有所帮助,还有一点要说明下:面向对象思维远比语言的选择更重要,所以无论Java、C++还是C#,不同的语法只是为了满足自己特定场合的用途,而核心思想才是最重要的,实现方式反而没那么需要重视(R语言也一样哦,你想学,我就提前学学教你hhh)!
宏观认知
有别于作者的排列方式,我想先宏观的串讲下概念,然后再填充细节。采用连续发问的方式来引发你的思考和明白没一项的场景需求:
1,为什么要有类?
相较于之前5章对面向过程编程层面的理解,我觉得面向对象编程的好处就是:可以不必从头创建新程序,而是用现有的一个模板去复制、去扩展,或添加更多,而且整个代码还非常有条理,可以控制访问权限、组织起来不会乱,还有就是理解起来也更加容易,而类就是用来完成面向对象的使命的。
2,面向对象的三大特性:继承、封装、多态?
依据面向对象编程的好处,我们可以确定,面向对象比面向过程的优势:可扩展、好组织、能控制访问权限。所以对应的类一定要具有这样的能力:封装类的细节、派生类型来扩展基类型的能力(包括数据和方法)、方法或类型的多态化实现。
3,如何实现封装这一特性(本篇只介绍封装部分,下一章介绍继承和多态)?
这就需要很好的组织类的成员,先来了解一下:实例字段、静态字段、实例方法、静态方法、普通构造函数(实例)、静态构造函数、嵌套类。用访问修饰符来限定以上内容的访问方式,当然,如果限定了private的字段,我【实例.字段】是看不到私有成员的,但是我又想给该字段赋值,或者想让数据在外部只读,而能内部修改,简而言之就是想让字段能具有更加细粒度的操作内容,怎么办呢,传统的方式是使用public的get和set方法,但是C#提供了属性来简化这一实现。
4,既然我都有了实例字段、实例方法,我为啥还需要静态的字段和方法?书上有个例子举的特别好:
类和对象都能关联数据。将类想象成模具,将对象想象成根据该模具浇铸的零件,可以更好地理解这一点。例如,一个模具拥有的数据可能包括:到目前为止已用模具浇铸的零件数、下个零件的序列号、当前注入模具的液态塑料的颜色以及模具每小时生产零件数量。类似地,零件也拥有它自己的数据:序列号、颜色以及生产日期/时间。虽然零件颜色就是生产零件时在模具中注入的塑料的颜色,但它显然不包含模具中当前注入的塑料颜色数据,也不包含要生产的下个零件的序列号数据。
设计对象时,程序员要考虑字段和方法应声明为静态还是基于实例。一般应将不需要访问任何实例数据的方法声明为静态方法,将需要访问实例数据的方法(实例不作为参数传递)声明为实例方法。静态字段主要存储对应于类的数据,比如新实例的默认值或者已创建实例个数。而实例字段主要存储和对象关联的数据。
5,扩展方法有啥意义?
其实我感觉没啥意义,就是如果一个已经写好的类缺少你比较需要的一个功能,可以通过扩展方法来创造一个扩展类出来,然后在里面写一个扩展方法,这样你的类的对象就可以直接调用该扩展方法了,就好像你的类本身就具有这样的方法一样。其实我感觉这种方式用的应该比较少,如果对象想实现一个类没有的方法,完全可以搞个派生类出来。
6,嵌套类有啥用?
如果一个类在它的包容类外部没有多大意义,就适合设计成嵌套类,换言之,这个类对于它的包容类比较专属,通过嵌套的方式可以限定它不被滥用。
7,为啥要有分部类和分部方法
我这么理解:其实说白了就是,一个类或方法如果在修改的时候有一部分老是不变,就把它单独抽成一个文件呗,避免反复无意义的重新生成,而且有时候如果多个程序员同时改一个类,虽然有git版本控制,但是拆开来岂不是更好。
对象和类
类的声明和实例化其实已经不必赘述了,需要注意的就是以下两点规范:
- 不要在一个源代码文件中放多个类
- 要用所含公共类型的名称命名源代码文件(如果你非要不听话的在源代码文件中放多个类)
而对象就是类new出来的,将数据和方法(行为)组合在一起。而访问修饰符可以限定其访问范围:
各种类型可使用的访问修饰符:
需要注意的是,这表明的是可见性!就是你能通过智能感知器关联看到!,还有就是成员默认私有!如果不为类的成员添加访问修饰符,默认的访问修饰符为private!
属性的用法
为啥要有属性,就像我在上文提到的,如果我又想保证不可见,有想不通过方法调用来赋值或者取值,使用属性多合适啊!
声明属性
1,简单的属性声明方式:
class Employee { // FirstName property public string FirstName { get { return _FirstName; } set { _FirstName = value; } } private string _FirstName; //字段 }
2,花式的使用表达式主题方法的声明方式,C#7.0支持:
public string LastName { get => _LastName; set => _LastName = value; } private string _LastName;
3,高端玩儿法:C#3.0支持自动属性:
// Title property public string Title { get; set; } // Manager property public Employee Manager { get; set; } public string Salary { get; set; } = "Not Enough"; //C#6.0支持直接赋值
属性设计规范
属性有如下几条使用规范,最需要注意的就是,属性代表数据,而方法代表行动,不用混为一谈。
- 要使用属性简化对简单数据的访问(只进行简单计算)。
- 避免从属性取值方法抛出异常。
- 要在属性抛出异常时保留原始属性值。
- 如果不需要额外逻辑,要优先使用自动实现的属性,而不是属性加简单支持字段。
当然也有一定的书写和命名规范:
- 考虑为支持字段和属性使用相同的大小写风格,为支持字段附加“_”前缀。但不要使用双下划线,它是为C#编译器保留的。
- 要使用名词、名词短语或形容词命名属性。
- 考虑让属性和它的类型同名。
- 避免用camelCase大小写风格命名字段。
- 如果有意义的话,要为Boolean属性附加“Is”,“Can”或“Has”前缀。
- 不要声明public或protected实例字段(而是通过属性公开)。
- 要用PascalCase大小写风格命名属性。
- 要优先使用自动实现的属性而不是字段。
- 如果没有额外的实现逻辑,要优先使用自动实现的属性而不是自己写完整版本。
总之优先使用自动实现的属性,使用PascalCase风格命名。
属性验证
属性的一个重要作用就是在设置值或者取值的时候做验证。在验证的时候,关键字value默认为传入参数,不需要单独声明,当然验证的时候自动属性就不好使喽。
public class Employee { // ... public void Initialize( string newFirstName, string newLastName) { // Use property inside the Employee // class as well LastName = newLastName; } // LastName property public string LastName { get => _LastName; set { // Validate LastName assignment if(value == null) { // Report error throw new ArgumentNullException(nameof(value)); } else { // Remove any whitespace around // the new last name value = value.Trim(); if(value == "") { throw new ArgumentException( "LastName cannot be blank.", nameof(value)); } else { _LastName = value; } } } } private string _LastName; }
还有一条重要规范就是:避免从属性外部(即使是属性所在的类,包括构造方法)访问属性的支持字段。也就是字段一切听属性的,保证修改的单一性。这里简单说说nameof关键字:主要作用是方便获取类型、成员和变量的简单字符串名称(非完全限定名),nameof可以用于获取具名表达式的当前名字的简单字符串表示(非完全限定名),注意这个非完全限定:
using static System.Console; using TML = System.ConsoleColor; internal class Program { private static void Main() { WriteLine(nameof(TML )); //输出TML ,因为它是当前的名字,虽然是指向System.ConsoleColor枚举的别名,但是由于TML是当前的名字,那么nameof运算符的结果就是"TML"。 WriteLine(nameof(System.ConsoleColor));//ConsoleColor } }
只读或只写的设定
这里以设置只读为例来介绍下几种设定方式:
1,方式一,传统方式,去掉set方法就行了,如果想赋值只能通过构造函数或方法对字段赋值。本类构造函数或方法不能修改属性值
class Employee { public void Initialize(int id) { // Use field because Id property has no setter; // it is read-only _Id = id.ToString(); //属性不能赋值了,只能通过这种方式 } // Id property declaration public string Id { get => _Id; // No setter provided //没有set方法就行 } private string _Id = default(string); }
2,方式二,只读自动属性方式,如果想赋值,只能通过初始化器或者:构造函数或方法(对字段赋值)。需要注意的是,如果只读的是引用,那么其实可以修改和写入该数组的值。本类构造函数或方法不能修改属性值
public class Class1 { public int[] Cells { get; } = { 2, 3, 4 }; } public class Class2 { private Class1 ctsClass1 = new Class1(); public void first() { ctsClass1.Cells[0] = 5; var test = new int[4]; // ctsClass1.Cells = test; //报错,因为是只读的,所以其实这里的支持字段是数组类型,只读的其实是引用 } }
3,方式三:get和set级别的访问修饰符,本类构造函数或方法可以修改属性值,实现如下:
class Program { static void Main() { Employee employee1 = new Employee(); employee1.Initialize(42); // ERROR: The property or indexer 'Employee.Id' // cannot be used in this context because the set // accessor is inaccessible //employee1.Id = "490"; //will not compile if you uncomment this line } } class Employee { public void Initialize(int id) { // Set Id property Id = id.ToString(); } // ... // Id property declaration public string Id { get => _Id; // Providing an access modifier is possible in C# 2.0 // and higher only private set => _Id = value; //私有化set } private string _Id; }