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管理系統等,作為高級語言開發效率高了,面臨的一個很重要的問題就是靈活定制,便於維護,那么這不就正是面向對象擅長了優點嘛,所以以我目前的經驗或者后來者也是做這方面來說,面向對象對開發項目是很重要的概念+實際應用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM