C#根据工作经验来谈谈面向对象

  • C#面向对象的三大特性:封装、继承、多态。
  • 这是一种特性,更是官方给我们的学习语法,但是我们根据过去的经验来思考一下,
    1. 到底什么是面向对象?
    2. 面向对象在我们实际开发中到底起着什么作用?
    3. 我们什么时候要用面向对象,他真的为我们解决了什么?
    4. 假如不用面向对象行不行?
  • 下面我们来逐步分析一下:

 

  • 到底什么是面向对象?
  1. 提到面向对象时,我们不得不提一下面向过程,面向过程典型的代表就是:C语言开发。
  2. C语言是面向过程,执行顺序由前到后,逻辑表达比较清晰,相对而言,这种逻辑开发更加符合我们日常的思维方式,因为我们平时做事也是按顺序思考;正因为符合我们的日常思维方式,优点:更容易理解,减少了反复实例化的过程,内存认知能力更快,因为不管什么语言都要最终编译成机器码(也就是二进制0 1)来处理,既然我没有那么多对象,所以本身资源分配会高;缺点:后期维护性和复用性降低,为什么呢,因为我就是按顺序执行的,你怎么让我复用,你要维护时,是不是也得从前向后去找我。
  3. 举个例子:比如我们在编写C时,我的入口主方法:
  4. #include "stdafx.h"
    #include <stdio.h>
    #include <Windows.h>
    #define _CRT_SECURE_NO_WARNINGS
    
    int main()
    {
        char chr[20];
        scanf_s("%s", &chr,sizeof(chr));
        myWrite(chr);
        system("pause");
        return 0;
    }
    int myWrite(char *chr)
    {
        printf("哈哈:%s\n", chr);
        return 0;
    }

    以上代码是执行不下去的,为什么,我既然是面向过程,你怎么能先调用后定义呢?显示不行,必须把myWrite()函数移到main()函数之上才行。或者

  5. 或者,你提前把我定义一下也行啊;如:
  6. int myWrite(char *chr);
    int main()
    {
        char chr[20];
        scanf_s("%s", &chr,sizeof(chr));
        myWrite(chr);
        system("pause");
        return 0;
    }
    int myWrite(char *chr)
    {
        printf("哈哈:%s\n", chr);
        return 0;
    }

    这就是典型的面型过程。

  7. 面向对象的典型代表就是:C#、C++、Java等,以C#为例说明。
    • 我们先来说面向对象的三大特性:封装性、继承性、多态性。    
    • 什么是封装:封装就是把所有处理细节统一起来,只预留调用的接口和参数约定,来完成功能流程的处理,这样被我封装的部分将根据不同的要求进行访问控制,最终完成需求。
    • 封装我们都是以类为模板进行,类里面可以有多个实现方法,每个封装的类或方法又有不同的访问级别。如下:
    • class Program
          {
              static void Main(string[] args)
              {
                  Student student = new Student();
                  int stuNum = student.stuCount();
                  Console.WriteLine(stuNum);
      
                  Console.ReadKey();
              }
          }
          public class Student
          {
              public int stuCount()
              {
                  return 10;
              }
          }

      简单封装了一个类Student,关于学生信息的处理都集中到这个类里,然后实例化调用相关方法。

      • 在实际使用过程中,封装主要用于两大方面:
        1. 功能性封装,比如一个事务下的,模块下的,同类型下的逻辑处理,这时候我们要考虑封装。
        2. 实体类处理,我们在处理数据实体类时,经常进行封装,一般是一个表或+扩展字段后,进行逻辑字段和属性的封装。
    • 什么是继承:在处理类对象时,对于有重复性思考的对象,我们要用继承来处理。并且类是单继承模式,类不能同时既继承A,又继承B;
        • 继承的作用主要在封装基础上,进行的扩展,为什么这样说呢?既然是继承,其实就是共享性东西的再封装,举例如下:
        • class Program
              {
                  static void Main(string[] args)
                  {
                      Car c = new Car("汽车类型");
          
                      Geely geely = new Geely();
                      geely.CarName = "吉利";
                      geely.Nationality = "China";
                      geely.SayCar(geely.CarName, geely.Nationality);
          
                      Console.ReadKey();
                  }
              }
              class Car
              {
                  string _carType = "";
                  public Car()
                  {
          
                  }
                  public Car(string carType) : this()
                  {
                      _carType = carType;
                  }
          
          
                  private string carName;
                  public string CarName
                  {
                      get { return carName; }
                      set { carName = value; }
                  }
              }
              class Geely : Car
              {
                  string _carName = "";
                  public Geely()
                  {
          
                  }
                  public Geely(string carName) : this()
                  {
                      _carName = carName;
                  }
                  private string nationality;
                  public string Nationality
                  {
                      get { return nationality; }
                      set { nationality = value; }
                  }
                  public void SayCar(string name, string nation)
                  {
                      Console.WriteLine("我叫" + _carName + "," + "我属于" + nation);
                      Console.WriteLine("我叫" + name + "," + "我属于" + nation);
                  }
              }

          我定义了一个Car类,是基类,又定义了一个Geely类,是子类,并且分别对构造函数进行了参数传递。运行结果如下:

        1. 第一个结果显示_carName为空,可见构造函数本身是不被继承的。

        2. 提取出了公共对象name,因为不管那个国家的车,都会有名字,单独对共享的东西进行了封装,所以说继承也是更进一步的封装。
      • 假如我就不想被继承怎么办?有办法我们提供了一种类叫密封类:格式如下: 
      • sealed class DaZhong
            {
        
            }
            class shanghaiDZ : DaZhong
            {
                  //这个时候继承是报错的,提示无法从密封类中继承,那么DaZhong本身就不能被任何子类继承了。
            }
    • 什么是多态?我相信,封装、继承作为面向对象的前两大特性,不难,很多人一提多态就晕,很正常,有时候你工作后是不是发现,不知道你学的多态到底在干什么,好像是不是项目中就不知道用没用到多态,那里用的,带着这个问题,我们来思考一下。
      • 多态多态,字面意思来说就是多种态度,既然是编代码嘛,那就是说在不同的语境下他会呈现不同的态度。
      • 为什么我们觉得封装和继承很好理解,但是一说多态都不知道是什么,如果这样感觉,那你就感觉对了,因为多态本身就什么也不是,多态是一个很抽象的概念,是一个来形容面向对象特性的一个关键词,而其实多态就好比一个大平台大容器,他带有的很多特点特色又组成了这个多态容器,而多态容器+继承+封装:又反应出了面向对象的特色。
      • 下面我们通过理论+实践例子,来具体分析一下多态这个大容器的一些特性;如下:
      1. 重写基类方法
        1. 例子如下:
          • //父类Car
                class Car
                {
                    public virtual void Fun()
                    {
                        Console.WriteLine("我是车类型");
                    }
                }
                //继承Car
                class DZCar:Car
                {
                    public override void Fun()
                    {
                        Console.WriteLine("我是大众");
                    }
                    public void Test()
                    {
                        Fun();
                        base.Fun();
                    }
                }
               static void Main(string[] args)
                    {
                        Car car = new Car();
                        car.Fun(); //父类fun
                        Car dzCar = new DZCar();
                        dzCar.Fun(); //子类fun
                        DZCar car2 = new DZCar();
                        car2.Fun();//子类fun
                        car2.Test();//子类fun,父类fun
            
                        Console.ReadKey();
                    }

            运行结果如下如右侧注释,说明:重写override后,结果就都是重写后的子类方法,其中base含义代表指向父类。

         2、隐藏基类方法

      1. 例子如下:
                • class MyBase
                      {
                          public virtual void Fun()
                          {
                              Console.WriteLine("我是基类虚方法");
                          }
                      }
                      class MyChild:MyBase
                      {
                          public new virtual void Fun()
                          {
                              Console.WriteLine("我是子类虚方法");
                          }
                      }
                  
                   MyChild mc = new MyChild();
                              mc.Fun();  //子类
                              MyBase mb = mc;
                              mb.Fun();  //父类
                              MyBase mb2 = new MyBase();
                              mb2.Fun();  //父类

                  运行结果如右侧注释,new作为隐藏基类方法那么结果就是根据定义的类型来决定输出父类还是子类,赋值给MyBase,所以就输出父类方法。

                  面向对象版计算器源码下载:

                      链接:https://pan.baidu.com/s/1bkBkoWp8RAUlii718wh_5Q
                      提取码:bbwg

                • 那么另一个问题来了,这只是目前面向对象的一个方面而已,利用这个例子我们分析一下,我们在实际开发中怎么去锻炼这个面向对象的思维呢,我又是怎么知道要做面向对象的设计呢?

                • 我个人总结以下几点:

                  1. 碰见问题时,先不要刻意的说就必须面向对象,就必须把这个瓜强扭起来,我们先按面向过程的思维逻辑顺下来。因为面向过程的思维逻辑相对比较符合我们的日常思维逻辑。

                  2. 当你面向过程的逻辑顺下来后,那么我们就要分析,那些是重用的,那些是独立的,那些可以封装、继承,那些又是抽象出来需要以后扩展的,这时候就需要分门别类,把一些特性和理论给利用起来,逐步实现面向对象的思维模式。

                  3. 当你初步有了面向对象思维后,那么更重要的一点就是项目实践,学了不用,等于0基础,过段时间肯定忘。

                  4. 要坚持一个原则,面向对象本没有固定对象,也没有哪一种面向后就绝对好,要形成自己的思路来面向对象,反正地基大家都是用的钢筋混凝土,怎么盖,怎么设计,你说了算。

                  5. 要实现第四点怎么盖,怎么设计,需要更多的实践和理论结合,才可以。

           

             小结后继续面向对象其他特性分析:

        3、抽象类abstract

  • 抽象类本身的概念就是把一些事物共性提取出来,设置为抽象基类,然后剩余的事情交给子类去扩展。
  • 用abstract关键字修饰,修饰类就是抽象类,修饰方法就是抽象方法
  • 抽象类定义的抽象方法不能有方法体;如果定义的是非抽象方法,可以有方法体,但是抽象类的目的就是为了子类扩展,一般我们在抽象类里定义非抽象方法没有什么作用,如果你设计时在抽象类里定义非抽象方法,可能是设计冗余。
  • 子类继承抽象类后,必须实现抽象方法,实现的关键字也是override
  • 抽象方法必须放在抽象类中,抽象类中允许有非抽象方法。
  • 如果子类也是抽象类,那么可以不实现抽象方法,也就是说不用override
  • 例子如下:
  • public abstract class Car
        {
            //字段_name
            private string _name;
            //属性Name
            public string Name { get; set; }
          //抽象方法
            public abstract void Fun();
            //非抽象方法F
            public void F()
            {
    
            }
            
        }
        //继承抽象类Car
        public abstract class DZCar : Car
        {
            //因为我本身也是抽象类,所以不用实现抽象方法
            public abstract void Test();
        }
       //继承抽象类DZCar
        public class SDZ:DZCar
        {
          //基类和父类都是抽象方法,继承下来后需要实现两个方法,否则报错,这就是抽象类的特点
            //实现基类方法Fun
            public override void Fun()
            {
               
            }
            //实现父类方法Test
            public override void Test()
            {
        
            }
        }

    以上例子说明了抽象类是什么,怎么定义、继承和实现抽象类

  • 那么一个很现实的问题来了,我们学会了虚方法virtual,又了解了抽象类定义abstract,这两个之间有什么区别,实际用时到底怎么区别定位呢?
  • 区别:
    • virtual abstract
      只能修饰方法,不能作为类修饰符 既可以修饰方法,也可以修饰类
      基类定义时必须有方法体 不能有方法体
      目的是为了让子类实现定义的方法,子类也可以不实现,不强制 目的是为了让子类实现,子类继承后必须实现方法体,除非子类也是虚方法
      虚方法所在的基类可以被实例化在赋值使用 抽象类不能被实例化
    • 比如我们做一个超市系统,超市里有很多种类的物品,我们经常需要设计一个录入物品的超市系统
    • 这时我发现,每件物品都会有名称和描述,那么我设计一个抽象类如下:
    • public abstract class SuperBase
          {
              private string _name;
              public string Name {
                  get { return _name; }
                  set
                  {
                      if (Name != "未分类")
                      {
                          _name = value;
                      }
                      else
                      {
                          _name = "未分类";
                      }
                  }
              }
              public abstract string Desc();
          }
          public class TypeCas : SuperBase
          {
      
              public override string Desc()
              {
                  Console.WriteLine("描述下" + base.Name);
                  return "";
              }
          }

      static void Main(string[] args)
      {
      TypeCas typecas = new TypeCas();
      typecas.Name = "可乐";
      typecas.Desc();

      }

      这个时候物品描述将被继承下来

    • 录入物品的时候我们发现不同的物品价钱也不是一样,那么怎么办呢?这时我就定义了一个虚方法virtual,如下例子:
    • public virtual decimal Price { get; set; }
      
       public override decimal Price
              {
                  get
                  {
                      return base.Price;
                  }
      
                  set
                  {
                      base.Price = value;
                  }
              }
      
        TypeCas typecas = new TypeCas();
        typecas.Price = 10;
        //好处如果别的分类新增了,直接
       TypeCas typecas = new TypeCas2();
      TypeCas typecas = new TypeCas3();

      因为抽象类不支持实例化,子类重写后继承实现了抽象类的抽象方法,这个是大家共享的,每个子类都必须且要实现

    • 虚方法重写后是为了子类某些态度的变化,子类要有自己的态度。
    • 而超市物品描述是每个物品都有的,价钱呢经常会变动,
    • 当然如果你说我就用抽象类来定义价钱,也可以的,只是通过例子来说明这两个类型的问题
  • 里氏转换
    • 就是基类和子类之间的互相转换
    • 子类继承基类,子类可直接转换成对应基类
    • 基类可以转换为相同类型下的子类
    • 例子如下:
    •     class A
          {}
          class B:A
          {}
      
              static void Main(string[] args)
              {
                  B b = new B();
                  A a = b; //没问题
                  B b2 = (B)a; // 可以强制转换,因为虽然是基类->子类转换,但是同类型下的可以,什么是同类型,就是说子类也转换成父类了,反过来父类可以强转子类
                  A a1 = new A();
                  // B b1 = (B)a1; //报错,不能强制转换
                  B b1;
                  //里氏转换判断
                  if(a1 is B)
                  {
                      b1 = (B)a1;
                  }
      
                  b1 = a1 as B;  //转换成功直接返回转换后结果,转换失败返回null
              }
  • this和base
    • this表示访问本类某字段
    • base表示访问基类某字段
  • 接口
    • 关键字修饰interface
    • 接口成员包含:方法、属性、索引、事件
    • 接口是对能力的抽象定义
    • 接口成员不能有访问修饰符、不需要方法体
public interface IFun
    {
        //方法
        void Desc();
        //属性
        string Name
        {
            get;
            set;
        }
        //索引
        string this[int index]
        {
            get;
            set;
        }
        //事件
        event Action MyEvent;

    }
  • 实现接口时有普通实现和显示实现两种方式
    1. 普通实现就是把接口成员实现
    2. 显示实现实现时前面把对应接口名带上,解决多继承接口时成员重名问题
      1. 如果是显示实现,实例化时,必须声明接口类型来接收:如下:
static void Main(string[] args)
        {
            Car c = new Car(); // 这是错的
            IFun i = new Car(); //这是对的
            i.Desc();
        }
  • 接口和抽象类之间区别:通俗的说,接口是底层设计,更注重行为,或者不影响类基本继承的一个设计,不会去要求和影响类后续的使用;
  • 抽象类已经有了基类的概念作用,类的继承要实现并且使用,已经作为一个大模板来定义了。    

 说到这里,我们总结一下:

  1. 其实面向对象不就是多个特性特征组成的语法,不管是接口、抽象类、虚方法,继承,封装等等,扩展性比面向过程更高了,耦合性更低了,大家都比较独立起来了,独立更多的就说成是子类的扩展和独立。
  2. 面向对象的地基有了后,其实面向对象里的扩展和设计很多很多,可以先学习一个,经验多了,慢慢总结,不要贪多。
  3. 假如不用面向对象行不行,答案是行,可以实现功能,也可以完成项目开发,因为面向对象就是一个设计。那为什么我们不管是C#、Java,作为主流语言要面向对象,那其实是工作方面、项目方面不一样,我们开发网站系统、OA管理系统等,作为高级语言开发效率高了,面临的一个很重要的问题就是灵活定制,便于维护,那么这不就正是面向对象擅长了优点嘛,所以以我目前的经验或者后来者也是做这方面来说,面向对象对开发项目是很重要的概念+实际应用。
posted @ 2019-03-25 14:47  Micc_it  阅读(1051)  评论(0编辑  收藏  举报