Day 10 我們學會使用 Class,把相關的資料與行為組織在一起。
例如:
class Order
{
public string Product = "";
public decimal Price;
public int Quantity;
public decimal CalculateTotal()
{
return Price * Quantity;
}
}
但目前有一個問題:
order.Price = -100m;
order.Quantity = -10;
Class 外部可以直接修改資料,Order 本身無法控制資料是如何被修改的。
因此今天要解決的問題是:
Class 如何控制自己的資料,可以怎麼被外部讀取與修改?
這就是:
Encapsulation(封裝) 與 Property(屬性)
public
private
get
set
private set
今天最重要的概念:
Public Field
↓
外部可以直接存取
↓
Property
分開讀取與設定
↓
private set
限制外部修改
↓
Class 掌握資料的存取方式
↓
Encapsulation
Day 10 的 Order:
class Order
{
public string Product = "";
public decimal Price;
public int Quantity;
}
因為 Field 都是 public,所以 Class 外部可以直接:
order.Price = -100m;
order.Quantity = -10;
也就是外部程式可以直接決定 Object 裡的資料。
真正的問題是:
Object 的資料不應該完全交給外部任意操作。
我們希望改成:
外部程式
↓
Class 提供的方式
↓
存取或修改資料
這就是 Encapsulation 的核心。
可以先簡單理解:
Encapsulation 就是讓 Class 自己管理資料,而不是讓外部程式隨意操作。
要控制 Class 的資料,首先要認識:
Access Modifier(存取修飾詞)
目前先掌握:
public
private
public例如:
public decimal Price;
public 代表 Class 外部也可以存取。
所以:
Console.WriteLine(order.Price);
order.Price = 500m;
都可以。
private如果改成:
private decimal _price;
private int _quantity;
private 代表:
只有目前 Class 內部可以存取。
例如 Class 內部可以:
class Order
{
private decimal _price;
private int _quantity;
public decimal CalculateTotal()
{
return _price * _quantity;
}
}
但 Class 外部不能直接:
order._price;
簡單整理:
| Access Modifier | Class 內部 | Class 外部 |
|---|---|---|
public |
✓ | ✓ |
private |
✓ | ✗ |
其他 Access Modifier 之後遇到時再介紹。
private 就好了嗎?如果:
class Order
{
private string _product = "";
private decimal _price;
private int _quantity;
}
外部確實不能直接修改資料。
但也出現另一個問題:
Console.WriteLine(order._price);
連正常讀取都做不到。
實際上我們可能希望:
Price
外部讀取
✓
外部直接修改
✗
也就是:
讀取與修改需要分開控制。
這就是 Property 出現的原因。
Property(屬性)是 Class 的一種成員。
可以先理解成:
Property 用來控制資料如何被讀取或設定。
例如:
public decimal Price
{
get;
set;
}
其中:
get
→ 讀取
set
→ 設定
所以:
Console.WriteLine(order.Price);
會透過 get 取得資料。
而:
order.Price = 500m;
會透過 set 設定資料。
剛才的 Property:
public decimal Price
{
get;
set;
}
通常可以簡寫成:
public decimal Price { get; set; }
這稱為:
Auto-implemented Property(自動實作屬性)
因此 Day 10 的:
public string Product = "";
public decimal Price;
public int Quantity;
可以改成:
public string Product { get; set; } = "";
public decimal Price { get; set; }
public int Quantity { get; set; }
目前不用深入 Compiler 如何保存資料。
先記住:
{ get; set; }
get
→ 可以讀取
set
→ 可以設定
即可。
Field:
public decimal Price;
Property:
public decimal Price { get; set; }
整理如下:
| Field | Property | |
|---|---|---|
| 範例 | public decimal Price; |
public decimal Price { get; set; } |
| 用途 | 直接保存資料 | 提供存取資料的方式 |
| 對外提供資料 | 通常不優先使用 | 一般較常使用 |
初學階段先記:
Class 對外提供資料時,通常會優先看到 Property,而不是 Public Field。
private set:可以讀,但不能直接修改雖然我們改成:
public decimal Price { get; set; }
但外部仍然可以:
order.Price = -100m;
因為 set 仍然是公開的。
如果希望:
外部讀取
✓
外部直接修改
✗
Class 內部修改
✓
可以改成:
public decimal Price { get; private set; }
現在外部可以:
Console.WriteLine(order.Price);
但不能:
order.Price = -100m;
因為:
get
→ public
set
→ private
可以理解成:
外部
│
├── 讀取 Price
│ ✓
│
└── 修改 Price
✗
Order Class
│
└── 修改 Price
✓
所以:
private set** 代表外部可以讀取 Property,但只有 Class 內部可以設定它。**
要注意:
private set** 解決的是「誰可以修改」的問題,不代表資料本身一定合法。**
例如 Class 內部仍然可以設定:
Price = -100m;
資料是否合法,之後介紹資料驗證與 Exception 時再進一步處理。
private set把 Order 改成:
class Order
{
public string Product { get; private set; }
public decimal Price { get; private set; }
public int Quantity { get; private set; }
public Order(
string product,
decimal price,
int quantity)
{
Product = product;
Price = price;
Quantity = quantity;
}
}
雖然 Class 外部不能:
order.Price = 500m;
但 Constructor 位於 Order Class 內部,因此:
Price = price;
可以正常設定。
建立 Object:
Order order =
new Order("TXF", 250.5m, 5);
流程:
外部
↓
new Order(...)
↓
Constructor
↓
Order 自己初始化資料
建立完成後,外部可以讀取:
Console.WriteLine(order.Product);
Console.WriteLine(order.Price);
Console.WriteLine(order.Quantity);
但不能直接修改:
order.Price = 500m;
這是一個很重要的觀念。
使用:
public decimal Price { get; set; }
雖然已經是 Property,但外部仍然可以:
order.Price = -100m;
所以:
使用 Property,不代表已經做好 Encapsulation。
真正重要的是:
Class 是否有控制外部可以如何存取與修改資料。
例如:
public decimal Price { get; private set; }
就限制:
外部
→ 可以讀取
外部
→ 不能直接修改
Class 內部
→ 可以修改
因此可以這樣理解:
Property
→ 語法工具
Encapsulation
→ 設計目的
把今天的內容整理起來:
class Order
{
public string Product { get; private set; }
public decimal Price { get; private set; }
public int Quantity { get; private set; }
public Order(
string product,
decimal price,
int quantity)
{
Product = product;
Price = price;
Quantity = quantity;
}
public decimal CalculateTotal()
{
return Price * Quantity;
}
}
建立:
Order order =
new Order("TXF", 250.5m, 5);
讀取資料:
Console.WriteLine(order.Product);
Console.WriteLine(order.Price);
Console.WriteLine(order.Quantity);
計算總額:
Console.WriteLine(
order.CalculateTotal()
);
但外部不能:
order.Price = 500m;
現在 Order 可以理解成:
Order
│
├── Properties
│ ├── Product
│ ├── Price
│ └── Quantity
│
├── Constructor
│ └── 初始化資料
│
└── Method
└── CalculateTotal()
從 Day 10 到 Day 11 的變化:
Day 10
Public Field
↓
外部可以直接存取
Day 11
Property
↓
private set
↓
Class 控制資料的存取方式
建立新的 Console Project:
dotnet new console -n Day11Practice
進入 Project:
cd Day11Practice
建立:
class Product
{
public string Name;
public decimal Price;
}
把:
public string Name;
public decimal Price;
改成 Auto Property。
讓:
Name
Price
符合:
外部可以讀取
✓
外部不能直接修改
✗
提示:
private set
加入 Constructor,讓下面的程式可以建立 Product:
Product product =
new Product("Keyboard", 1500m);
接著輸出:
Console.WriteLine(product.Name);
Console.WriteLine(product.Price);
最後嘗試:
product.Price = 1800m;
觀察為什麼無法直接修改。
今天我們解決的問題是:
Class 如何控制自己的資料,可以怎麼被外部讀取與修改?
核心就是:
Encapsulation
public
→ Class 外部可以存取
private
→ 只有 Class 內部可以存取
例如:
public decimal Price { get; set; }
其中:
get → 讀取
set → 設定
private setpublic decimal Price { get; private set; }
代表:
外部讀取
✓
外部直接修改
✗
Class 內部修改
✓