【C#本质论 六】类-从设计的角度去认知(封装)(上)

简介: 【C#本质论 六】类-从设计的角度去认知(封装)(上)

从这一章开始,基本就脱离了结构化编程的思维,所以我的博客排版方式也会摒弃之前的节选方式,而是深入的去理解和描述内容,而这本书给我带来的惊喜就是:能够从设计的角度去帮助我理解为何类要如此设计,其设计缘由是什么,我觉得这一点远比单纯的告诉你怎么用更重要!感觉这一章的干货特别多,可以让我的面向对象思维上一个小小的台阶,这里将自己的学习笔记以及自己的理解整理分享出来,希望对大家都有所帮助,还有一点要说明下:面向对象思维远比语言的选择更重要,所以无论Java、C++还是C#,不同的语法只是为了满足自己特定场合的用途,而核心思想才是最重要的,实现方式反而没那么需要重视(R语言也一样哦,你想学,我就提前学学教你hhh)!

宏观认知

有别于作者的排列方式,我想先宏观的串讲下概念,然后再填充细节。采用连续发问的方式来引发你的思考和明白没一项的场景需求:

1,为什么要有类?

相较于之前5章对面向过程编程层面的理解,我觉得面向对象编程的好处就是:可以不必从头创建新程序,而是用现有的一个模板去复制、去扩展,或添加更多,而且整个代码还非常有条理,可以控制访问权限、组织起来不会乱,还有就是理解起来也更加容易,而类就是用来完成面向对象的使命的

2,面向对象的三大特性:继承、封装、多态?

依据面向对象编程的好处,我们可以确定,面向对象比面向过程的优势:可扩展、好组织、能控制访问权限。所以对应的类一定要具有这样的能力:封装类的细节、派生类型来扩展基类型的能力(包括数据和方法)、方法或类型的多态化实现

3,如何实现封装这一特性(本篇只介绍封装部分,下一章介绍继承和多态)?

这就需要很好的组织类的成员,先来了解一下:实例字段、静态字段、实例方法、静态方法、普通构造函数(实例)、静态构造函数、嵌套类。用访问修饰符来限定以上内容的访问方式,当然,如果限定了private的字段,我【实例.字段】是看不到私有成员的,但是我又想给该字段赋值,或者想让数据在外部只读,而能内部修改,简而言之就是想让字段能具有更加细粒度的操作内容,怎么办呢,传统的方式是使用public的get和set方法,但是C#提供了属性来简化这一实现。

4,既然我都有了实例字段、实例方法,我为啥还需要静态的字段和方法?书上有个例子举的特别好:

类和对象都能关联数据。将类想象成模具,将对象想象成根据该模具浇铸的零件,可以更好地理解这一点。例如,一个模具拥有的数据可能包括:到目前为止已用模具浇铸的零件数、下个零件的序列号、当前注入模具的液态塑料的颜色以及模具每小时生产零件数量。类似地,零件也拥有它自己的数据:序列号、颜色以及生产日期/时间。虽然零件颜色就是生产零件时在模具中注入的塑料的颜色,但它显然不包含模具中当前注入的塑料颜色数据,也不包含要生产的下个零件的序列号数据。

设计对象时,程序员要考虑字段和方法应声明为静态还是基于实例。一般应将不需要访问任何实例数据的方法声明为静态方法,将需要访问实例数据的方法(实例不作为参数传递)声明为实例方法。静态字段主要存储对应于类的数据,比如新实例的默认值或者已创建实例个数。而实例字段主要存储和对象关联的数据

5,扩展方法有啥意义?

其实我感觉没啥意义,就是如果一个已经写好的类缺少你比较需要的一个功能,可以通过扩展方法来创造一个扩展类出来,然后在里面写一个扩展方法,这样你的类的对象就可以直接调用该扩展方法了,就好像你的类本身就具有这样的方法一样。其实我感觉这种方式用的应该比较少,如果对象想实现一个类没有的方法,完全可以搞个派生类出来

6,嵌套类有啥用?

如果一个类在它的包容类外部没有多大意义,就适合设计成嵌套类,换言之,这个类对于它的包容类比较专属,通过嵌套的方式可以限定它不被滥用。

7,为啥要有分部类和分部方法

我这么理解:其实说白了就是,一个类或方法如果在修改的时候有一部分老是不变,就把它单独抽成一个文件呗,避免反复无意义的重新生成,而且有时候如果多个程序员同时改一个类,虽然有git版本控制,但是拆开来岂不是更好。

对象和类

类的声明和实例化其实已经不必赘述了,需要注意的就是以下两点规范:

  1. 不要在一个源代码文件中放多个类
  2. 要用所含公共类型的名称命名源代码文件(如果你非要不听话的在源代码文件中放多个类)

而对象就是类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支持直接赋值

属性设计规范

属性有如下几条使用规范,最需要注意的就是,属性代表数据,而方法代表行动,不用混为一谈

  1. 要使用属性简化对简单数据的访问(只进行简单计算)。
  2. 避免从属性取值方法抛出异常。
  3. 要在属性抛出异常时保留原始属性值。
  4. 如果不需要额外逻辑,要优先使用自动实现的属性,而不是属性加简单支持字段

当然也有一定的书写和命名规范:

  • 考虑为支持字段和属性使用相同的大小写风格,为支持字段附加“_”前缀。但不要使用双下划线,它是为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;
    }


相关文章
|
2月前
|
C#
C#学习相关系列之数据类型类的三大特性(二)
C#学习相关系列之数据类型类的三大特性(二)
|
2月前
|
C#
58.c#:directory类
58.c#:directory类
13 0
|
2月前
|
C#
57.c#:directorylnfo类
57.c#:directorylnfo类
13 0
|
2月前
|
监控 C#
55.c#:file类
55.c#:file类
17 1
|
2月前
|
算法 C#
54.c#:random类
54.c#:random类
15 1
|
2月前
|
C#
51.c#:string类的静态方法
51.c#:string类的静态方法
21 1
|
2月前
|
C#
27.c#关键字sealed修饰类
27.c#关键字sealed修饰类
12 0
|
4月前
|
Java C#
C# 面向对象编程解析:优势、类和对象、类成员详解
OOP代表面向对象编程。 过程式编程涉及编写执行数据操作的过程或方法,而面向对象编程涉及创建包含数据和方法的对象。 面向对象编程相对于过程式编程具有几个优势: OOP执行速度更快,更容易执行 OOP为程序提供了清晰的结构 OOP有助于保持C#代码DRY("不要重复自己"),并使代码更易于维护、修改和调试 OOP使得能够创建完全可重用的应用程序,编写更少的代码并减少开发时间 提示:"不要重复自己"(DRY)原则是有关减少代码重复的原则。应该提取出应用程序中常见的代码,并将其放置在单一位置并重复使用,而不是重复编写。
51 0
|
2月前
|
C#
深入C#中的String类
深入C#中的String类
11 0
|
2月前
|
C#
C#学习系列相关之多线程(二)----Thread类介绍
C#学习系列相关之多线程(二)----Thread类介绍