C# 新手入門 物件導向 OOP (1) 類別 屬性 方法

by | 11 月 17, 2024 | 程式 | 0 comments

Views: 22

物件導向算是新手最常學到這邊就棄坑的部分,不過筆者認為是這樣,很多技能要實際使用一陣子才會有所認識,所以蠻多我都認為一開始學矇矇的沒關係,有一定的印象,多數時候先求看的懂程式就好。

跟大家說個好消息,基本上程式設計自從有函數function、物件導向Object-oriented後,就沒什麼畫時代大型變化,所以大概這關過了之後程式設計就打破最難的關卡。然後建議大家先別管書上有什麼複雜的抽象、泛型等等高級作法,我會從基本簡單的開始。

微軟的官方教學

class (類別)

class(類別)與object(物件)是個不容易找到現實中類比,我認為比較接近的概念是建築設計圖。

  • 程式設計師寫類別 → 建築師繪製設計圖。
  • 程式執行時將類別實體化(Instance)為物件 → 營造廠照著設計圖把房子蓋出來。

Instance 實體化

電腦執行程式時,遇到要將物件實體化會進行下列動作

  1. 在記憶體找塊空間
  2. 把class內容放一份放到那塊空間

簡單的說,實體化就是把物件具現化(獵人漫畫的用詞),類似在地球上租塊地按照藍圖把房子蓋起來的概念。

object 物件

當房子蓋好後,就可以開始使用這間房子搬進去住了。

//假設有個class叫做Bank,instance(實體化)object(物件)命名為bank
Bank bank = new Bank();

工作時候華人工程師會說,把物件new出來,就是指將class這樣new成一個變數。

物件導向程式設計起步走

到這邊先開一個新專案,假設我們要來設計銀行存款系統。

開新專案

開一個主控台專案

主控台專案

SundayBank

名字這邊我取SunnyBank 你可以照自己喜歡的取名。

設計存款帳號類別Account class

注意 程式語言的class要打小寫的

image

這邊我先解釋一下C#的程式碼

主要分為方案、專案,概念其實很簡單,一個方案下可以有很多個專案,然後如果是大型專案就會一個方案下有十幾個專案。

  1. OO系統方案
    • 網站後台專案
    • 網站前台WebApi專案
    • 底層類別專案
    • 共用函數專案
    • 資料庫存取專案
    • 系統排程專案
    • 單元測試專案

這邊因為才新開,所以只會有一個方案跟一個專案

專案

底下那個C# SundayBank就是專案,上面紫色圖示的是方案。

新增類別檔class

新增類別

(1)在SundayBank專案按下右鍵→(2)加入(Add)→(3)類別

增加Account.cs

class1.cs → 名稱輸入Account.cs,Account就是我們類別的命名,在C#的設計習慣類別名稱會使用大寫開頭為主,不過這點如果學員開始工作,就看各公司對程式的命名規定(但通常沒有)。

Account.cs

這邊我先解釋一下

//using 指的是參考了哪些類別(對..程式碼有很多內建的類別可以拿來用)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//namespace 用來區分這個class要放在那邊用的,我們先別管
namespace SundayBank
{
    //internal 這是存取範圍,指這個class只能在專案內呼叫,不能被其他專案呼叫
    //class 就是class
    // Account → class的名稱
    internal class Account
    {
    }
}

物件存取範圍

官方說明 協助工具層級 – C# reference | Microsoft Learn

官方列了七個範圍,新手先記最常用這兩個

  1. public 不限制。
  2. private 最嚴格限制,只有class內可以存取。
  3. 沒定義 = private

Property 屬性(欄位)

一個銀行帳號裡面有哪些重要資訊:

  1. 存戶名 → Name
  2. 帳號號碼 → Number
  3. 開戶時間 → CreateTime
  4. 目前現金存款 → Cash
  5. 存戶身分證字號 → RocId

這些開戶表格上要跟著記在戶頭資料中的,就是所謂的屬性。接下來將屬性補上如下,這邊練習時先將存取範圍都訂為public。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    
  public class Account
  {
    public string Name{get;set;}
    public string Number{get;set;}  
    public DateTime CreateTime{get;set;} 
    public decimal Cash{get;set;} 
    public string RocId{get;set;} 
  }
}

屬性命名習慣

官方文件 命名方針 – Framework Design Guidelines | Microsoft Learn

C# 普遍上public的屬性命名都會是字首大寫。如果命名是兩個單字則要改為兩個字首大寫。

好的範例:Number、Name

不好的範例:number、User_Name、USER。

屬性的註解

很多書都是教這樣寫註解,不過我個人非常的不推薦

//存戶名稱
public string Name { get; set;} //也有寫在後面的

欄位屬性有標準的註解方式,作法則是

C#屬性註解寫法image

在屬性上方輸入三個斜線,系統會自動長出標準的格式

/// <summary>
/// 存戶名
/// </summary>
public string Name { get; set;}

這樣寫的好處是只要用到這個屬性的地方,滑鼠移動上去都會顯示你的註解,對於後續維護上非常有幫助。

註解顯示

為屬性補上註解

/// <summary>
/// 銀行帳號
/// </summary>
public class Account
{
	/// <summary>
	/// 戶名
	/// </summary>
	public string Name { get; set; }
	
	/// <summary>
	/// 帳號 13碼 0000000-666666
	/// </summary>
	public string Number { get; set; }
	
	/// <summary>
	/// 開戶時間
	/// </summary>
	public DateTime CreateTime { get; set; }
	
	/// <summary>
	/// 目前存款
	/// </summary>
	public decimal Cash { get; set; }
	
	/// <summary>
	/// 身分證字號
	/// </summary>
	public string RocId { get; set; }
}

Method()方法(函數)

函數

如果學過早期程式設計的人,對函數一定不陌生,所有的程式語言靠函數來達成模組化設計。所以在學習Method之前我們先看一下什麼是函數。

//注意 這不是C# 是C語言
回傳類型 函數名稱(輸入值)
{

}

例如

//注意 這不是C# 是C語言
// 簡易家法的函數
int Add(int a ,int b)
{
    return a + b;
}

//在某處呼叫使用函數來使用
int a;
a = Add(10+20);

函數有幾個重點

  1. 一段程式碼
  2. 把重覆的程式碼抽出來共用
  3. 固定輸出與輸入的類型
  4. 隱藏複雜的邏輯
  5. 每個函數內的變數都是彼此獨立不互相干擾

函數這個概念在程式設計上是很重要的里程碑,後續程式設計的變化其實就不大,如果你說物件導向不就是很大的變化,我認為物件則是一個更複雜版的函數而已,能夠把相關的屬性與函數集中在一段程式當中。


方法Method

多數物件導向的程式語言的函數就是類別中的方法,但功能更多。

  1. 有存取範圍,常用的是
    • public 大家都能存取
    • private 只有類別內部能存取
  2. 可以不同的輸入值,但相同名稱的函數(稱為多載或多型、在後面的程式會講到這點)。

方法的命名重點

  1. 方法名稱,大駝峰 upper camel case
  2. 輸入值,小駝峰 lower camel case
public classs 物件名稱
{
  public 回傳類型 方法名稱 (輸入值)
  {

  }
}

那們我們來擴充一下方法,假設我要做一個存款的功能

/// <summary>
/// 存款
/// </summary>
/// <param name="money">存款金額</param>
public void Save(decimal money)
{
    //驗證不能存負值
    if (money < 0)
    {
        return;
    }
    Cash += money;
}
完整的程式 Account.cs 按鍵頭展開
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    /// <summary>
    /// 銀行帳號
    /// </summary>
    public class Account
    {
        /// <summary>
        /// 戶名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 帳號 13碼 0000000-666666
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 開戶時間
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 目前存款
        /// </summary>
        public decimal Cash { get; set; }

        /// <summary>
        /// 身分證字號
        /// </summary>
        public string RocId { get; set; }

        /// <summary>
        /// 存款
        /// </summary>
        /// <param name="money">存款金額</param>
        public void Save(decimal money)
        {
            //驗證不能存負值
            if (money < 0)
            {
                return;
            }

            Cash += money;
        }
    }
}

這個寫Method 有幾個重點可以讓你寫起來更省力

  1. 一個Method盡量只做一件事情。
  2. 相關驗證寫Method前面。
  3. 命名要能展現「意圖」,例如SearchByName、Append。

新手寫程式的時候先記住這兩點

第一點:寫程式要寫的快有個很重要的要素,讀程式的時間比寫程式時間多,開發中八成的時間都在「看」程式,真的「寫」程式時間只有佔兩成,程式本身如果容易讀,你開發的時間就會大幅縮短。

第二點:無論是Class或Method,不要太多功能塞在一起, 這種塞在一起的作法英文為Supper Class或God Class,中文會叫做萬用類別,這樣做雖然感覺開發很快很直覺,但會造成超級難維護,最後你就會因為不想維護這個可怕的坑,然後離職。

我們目前已經得到一個可以存款的小類別了,現在可以開始寫主程式。

如何使用寫好的類別

把類別class變成物件object會經過instance的過程 ,像是把設計圖蓋成房子。

//跟新增一個變數一樣
類別名稱 物件名稱 = new 類別名稱();
//將Account intance 名為 peterAccount的類別
Account peterAccount = new Account();
Account maryAccount = new Account();
Account wishAccount = new Account();

就像用同一個設計圖蓋三間房子,裡面的格局都相同,但門牌號碼不同。

進行開戶與存款

先回到主程式Program.cs中

Program.cs

  1. 假設我們有個存戶叫做Petter Chen
  2. 要開一個戶頭編號叫做7000199-111122
  3. 開戶存入2000元
//程式碼要寫在Main當中
static void Main(string[] args)
{
    //建立帳戶
    Account petersAccount = new Account();

    //設定銀行帳戶資料
    petersAccount.Name = "Peter Chen";
    petersAccount.Number = "7000199-111122";
    petersAccount.CreateTime = DateTime.Now;

    //進行存款 呼叫Save方法
    petersAccount.Save(2000);

    //輸出帳戶資訊
    Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
    Console.ReadKey();
}

開戶執行結果

開戶執行結果

多增加幾戶

static void Main(string[] args)
{
    //建立Peter的帳戶
    Account petersAccount = new Account();
    petersAccount.Name = "Peter Chen";
    petersAccount.Number = "7000199-111122";
    petersAccount.CreateTime = DateTime.Now;
    
    //進行存款 呼叫Save方法
    petersAccount.Save(2000);

    //建立Marry的帳戶
    Account marryAccount = new Account();
    marryAccount.Name = "Marry Wang";
    marryAccount.Number = "7000199-111332";
    marryAccount.CreateTime = DateTime.Now;

    //進行存款 呼叫Save方法
    marryAccount.Save(1000);
    marryAccount.Save(500);
    marryAccount.Save(50);



    //建立Marry的帳戶
    Account kenAccount = new Account();
    kenAccount.Name = "Ken Lee";
    kenAccount.Number = "7000189-132237";
    kenAccount.CreateTime = DateTime.Now;

    //進行存款 呼叫Save方法
    kenAccount.Save(1000);

    //輸出帳戶資訊
    Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
    Console.WriteLine($"帳戶名稱:{marryAccount.Name},帳戶號碼:{marryAccount.Number},開戶時間:{marryAccount.CreateTime},存款:{marryAccount.Cash}");
    Console.WriteLine($"帳戶名稱:{kenAccount.Name},帳戶號碼:{kenAccount.Number},開戶時間:{kenAccount.CreateTime},存款:{kenAccount.Cash}");
    Console.ReadKey();
}

開戶結果

目前的Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //建立Peter的帳戶
            Account petersAccount = new Account();
            petersAccount.Name = "Peter Chen";
            petersAccount.Number = "7000199-111122";
            petersAccount.CreateTime = DateTime.Now;
            
            //進行存款 呼叫Save方法
            petersAccount.Save(2000);

            //建立Marry的帳戶
            Account marryAccount = new Account();
            marryAccount.Name = "Marry Wang";
            marryAccount.Number = "7000199-111332";
            marryAccount.CreateTime = DateTime.Now;

            //進行存款 呼叫Save方法
            marryAccount.Save(1000);
            marryAccount.Save(500);
            marryAccount.Save(50);

            //建立Marry的帳戶
            Account kenAccount = new Account();
            kenAccount.Name = "Ken Lee";
            kenAccount.Number = "7000189-132237";
            kenAccount.CreateTime = DateTime.Now;

            //進行存款 呼叫Save方法
            kenAccount.Save(1000);

            //輸出帳戶資訊
            Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{marryAccount.Name},帳戶號碼:{marryAccount.Number},開戶時間:{marryAccount.CreateTime},存款:{marryAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{kenAccount.Name},帳戶號碼:{kenAccount.Number},開戶時間:{kenAccount.CreateTime},存款:{kenAccount.Cash}");
            Console.ReadKey();
        }
    }
}

物件設定初始值

官方說明 如何使用物件初始設定式初始化物件 – C# | Microsoft Learn

如果class本身的Property 有設定為public {set;}{get;set;}可以在new物件的時候直接設定初始值

例如我們戶名Property 為 public string Name { get; set; }

這樣我程式就能改為

物件初始化

Account petersAccount = new Account()
{
    Name = "Peter Chen",
    Number = "7000199-111122",
    CreateTime = DateTime.Now,
};

直接在new的時候就設定好object的初始值是比較漂亮的作法,程式碼的效率也會提升,作法是在new物件時,將object後加入{} ,然後在{}內寫入初始值,如有多個屬性要設定可以透過逗點分隔。

自動化

自動重構

自動重構2

Visual Studio其實很先進,會對你程式碼提出一些建議,在可以改善的程式碼底下標記綠色線。只要先點綠色線後,最前面會出現一個小燈泡,點下去就會出現建議選項,在這邊按下去系統就會自動幫你把程式碼變成物件初始值。

重構程式碼-改為物件初始值

寫程式的一個概念就是要時常整理跟優化你的程式碼,這個動作在程式設計稱為重構。雖然不建議一開始就做所謂的最佳化,但是補補註解,調整一些寫法對於後續開發是非常重要的。

重構後的Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //建立Peter的帳戶
            Account petersAccount = new Account()
            {
                Name = "Peter Chen",
                Number = "7000199-111122",
                CreateTime = DateTime.Now,
            };
            
            //進行存款 呼叫Save方法
            petersAccount.Save(2000);

            //建立Marry的帳戶
            Account marryAccount = new Account
            {
                Name = "Marry Wang",
                Number = "7000199-111332",
                CreateTime = DateTime.Now
            };

            //進行存款 呼叫Save方法
            marryAccount.Save(1000);
            marryAccount.Save(500);
            marryAccount.Save(50);

            //建立Marry的帳戶
            Account kenAccount = new Account
            {
                Name = "Ken Lee",
                Number = "7000189-132237",
                CreateTime = DateTime.Now
            };

            //進行存款 呼叫Save方法
            kenAccount.Save(1000);

            //輸出帳戶資訊
            Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{marryAccount.Name},帳戶號碼:{marryAccount.Number},開戶時間:{marryAccount.CreateTime},存款:{marryAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{kenAccount.Name},帳戶號碼:{kenAccount.Number},開戶時間:{kenAccount.CreateTime},存款:{kenAccount.Cash}");
            Console.ReadKey();
        }
    }
}

作業1-滾利息

  1. 設計一個方法,AddInterest(decimal interest) ,
  2. 年利率利息設定為0.70

作業2-重新命名Save方法

  1. 把存款的英文修正為Deposit(),正確的英文單字是這個。
  2. 測試修改後的程式

作業3-增加取款Withdraw方法

  1. 將Account.cs增加提款的Method → Withdraw()
  2. 提款動作要做的是→1檢查餘額是否足夠,不夠返回false,足夠進行扣款並返回true。
  3. 提款成功返回true,餘額不足返回false。
  4. 提款上限單筆為20,000。

作業4 – 重構程式碼-採用List

  1. 將這三個人的帳戶存入List<Account> bankAccount 這個List當中。
  2. for印出每個人的帳號
  3. foreach印出每個人的帳戶
  4. 每個人戶頭都存入3000元,當作發振興卷。

作業1 滾利息解答

執行畫面

C#物件導向 作業一 執行畫面

關鍵部分

/* Account.cs內 */

/// <summary>
/// 設定利息
/// </summary>
public  decimal InterestRate { get; set; }

/// <summary>
/// 滾利息
/// </summary>
/// <param name="days">利息天數</param>
/// <returns>本次利息金額</returns>
public decimal AddInterest(int days)
{
    // 利息計算公式:利息 = 本金 * 利率 * 天數 / 365
    decimal interest = Cash * InterestRate * days / 365;
    // 利息存入本金
    Cash += interest; 
    return interest;
}

完整程式

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //設定利率,參考郵局活存利率為0.830%
            const decimal interestRate = 0.0083m;

            //建立Peter的帳戶
            Account petersAccount = new Account()
            {
                Name = "Peter Chen",
                Number = "7000199-111122",
                CreateTime = DateTime.Now,
                InterestRate = interestRate
            };

            //進行存款 呼叫Save方法
            petersAccount.Save(2000);

            //建立Marry的帳戶
            Account marryAccount = new Account
            {
                Name = "Marry Wang",
                Number = "7000199-111332",
                CreateTime = DateTime.Now,
                InterestRate = interestRate
            };

            //進行存款 呼叫Save方法
            marryAccount.Save(1000);
            marryAccount.Save(500);
            marryAccount.Save(50);

            //建立Marry的帳戶
            Account kenAccount = new Account
            {
                Name = "Ken Lee",
                Number = "7000189-132237",
                CreateTime = DateTime.Now,
                InterestRate = interestRate
            };

            //進行存款 呼叫Save方法
            kenAccount.Save(1000);

            //輸出帳戶資訊
            Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{marryAccount.Name},帳戶號碼:{marryAccount.Number},開戶時間:{marryAccount.CreateTime},存款:{marryAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{kenAccount.Name},帳戶號碼:{kenAccount.Number},開戶時間:{kenAccount.CreateTime},存款:{kenAccount.Cash}");
            Console.WriteLine("------------------------");
            Console.WriteLine("增加一年利息");
            Console.WriteLine("------------------------");
            //計算利息
            petersAccount.AddInterest(365);
            marryAccount.AddInterest(365);
            kenAccount.AddInterest(365);
            //顯示增加利息後的帳戶資訊
            Console.WriteLine($"帳戶名稱:{petersAccount.Name},帳戶號碼:{petersAccount.Number},開戶時間:{petersAccount.CreateTime},存款:{petersAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{marryAccount.Name},帳戶號碼:{marryAccount.Number},開戶時間:{marryAccount.CreateTime},存款:{marryAccount.Cash}");
            Console.WriteLine($"帳戶名稱:{kenAccount.Name},帳戶號碼:{kenAccount.Number},開戶時間:{kenAccount.CreateTime},存款:{kenAccount.Cash}");

            Console.ReadKey();
        }
    }
}
Account.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;

namespace SundayBank
{
    /// <summary>
    /// 銀行帳號
    /// </summary>
    public class Account
    {
        /// <summary>
        /// 戶名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 帳號 13碼 0000000-666666
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 開戶時間
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 目前存款
        /// </summary>
        public decimal Cash { get; set; }

        /// <summary>
        /// 身分證字號
        /// </summary>
        public string RocId { get; set; }

        /// <summary>
        /// 存款
        /// </summary>
        /// <param name="money">存款金額</param>
        public void Save(decimal money)
        {
            //驗證不能存負值
            if (money < 0)
            {
                return;
            }

            Cash += money;
        }

        /// <summary>
        /// 設定利息
        /// </summary>
        public  decimal InterestRate { get; set; }

        /// <summary>
        /// 滾利息
        /// </summary>
        /// <param name="days"></param>
        /// <returns></returns>
        public decimal AddInterest(int days)
        {
            // 利息計算公式:利息 = 本金 * 利率 * 天數 / 365
            decimal interest = Cash * InterestRate * days / 365;
            // 利息存入本金
            Cash += interest; 
            return interest;
        }
    }
}

作業2 重新命名Save方法 解答

Visual studio重新命名函數
顯示命名設定,AI幫我補上建議名稱

顯示命名設定,AI幫我補上建議名稱

image

我有按下Shitf+Enter,對要重新命名的部分進行檢查,這是Visual Studio 2022才有的。

重新命名的注意事項

Visual Studio本身有內建一個重新命名的方法,它會順便把所有引用到的部分也一起重新命名。

如果開發的版比較舊,在使用上要注意它不會重新命名這幾個地方:

  • .cshtml (MVC的View)
  • .aspx (Webform的View)
  • 註解字串

這時候就要使用Ctrl+F ,用命名前的名稱去搜尋程式碼看有那邊使用過的,有個小技巧是可以先搜尋加上「.」的部分,代表有函數搜尋這部分。

0 Comments

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *