Các mẫu bất biến
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 34 trên 70.
Tính bất biến đưa tính đóng gói đến mức tối đa: một khi đối tượng được tạo, nó không bao giờ thay đổi. Điều này loại bỏ hẳn nhiều loại lỗi vì bạn không bao giờ phải lo lắng về việc có thứ gì đó bất ngờ sửa đổi dữ liệu của mình.
Cách đơn giản nhất để tạo một lớp bất biến là dùng các trường readonly cùng hàm khởi tạo thiết lập mọi giá trị và không cung cấp bộ thiết lập:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Sau khi tạo Point, tọa độ của nó không thể thay đổi. Nhưng nếu bạn cần một phiên bản đã chỉnh sửa thì sao? Thay vì thay đổi đối tượng hiện có, bạn trả về một đối tượng mới:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Điểm ban đầu vẫn không thay đổi, còn bạn nhận được một điểm mới với các giá trị đã cập nhật. Mẫu này được dùng xuyên suốt .NET. Chẳng hạn, chuỗi là bất biến. Khi gọi ToUpper(), bạn nhận được một chuỗi mới thay vì sửa đổi chuỗi ban đầu.
Đối tượng bất biến vốn an toàn khi chạy trong nhiều luồng vì không đoạn code nào có thể thay đổi chúng sau khi được tạo. Chúng cũng dễ suy luận hơn vì bạn biết trạng thái của chúng sẽ không bao giờ thay đổi ngoài dự kiến.
Thử thách
DễHãy xây dựng một hệ thống tiền tệ bất biến để minh họa cách thiết kế đối tượng sao cho không bao giờ thay đổi sau khi được tạo. Bạn sẽ tạo một lớp Money, trong đó mỗi thao tác trả về một đối tượng mới thay vì sửa đổi đối tượng ban đầu, một mẫu thiết kế giúp code an toàn hơn và dễ suy luận hơn.
Bạn sẽ tổ chức code trong hai tệp:
Money.cs: Tạo một lớpMoneybất biến trong không gian tênFinance. Lớp tiền tệ của bạn cần có:- Hai trường
readonly:Amount(decimal) vàCurrency(string) - Một hàm khởi tạo thiết lập cả hai giá trị. Sau khi được tạo, đối tượng Money không thể thay đổi
- Một phương thức
Add(decimal value)trả về một đối tượngMoneymới có số tiền tăng lên (cùng đơn vị tiền tệ) - Một phương thức
Subtract(decimal value)trả về một đối tượngMoneymới có số tiền giảm xuống (cùng đơn vị tiền tệ) - Một phương thức
Convert(string newCurrency, decimal rate)trả về một đối tượngMoneymới, trong đó số tiền được nhân với tỷ giá và đơn vị tiền tệ được đổi sang đơn vị mới - Một phương thức
GetDisplay()trả về một chuỗi theo định dạng"{Amount} {Currency}"
Moneyhoàn toàn mới!- Hai trường
Program.cs: Trong tệp chính, hãy minh họa tính bất biến. Tạo một đối tượngMoneyban đầu, rồi thực hiện các thao tác trên đối tượng đó trong khi vẫn giữ tham chiếu đến cả đối tượng ban đầu lẫn các kết quả. Điều này sẽ chứng minh rằng đối tượng ban đầu không bao giờ thay đổi, kể cả sau nhiều thao tác.
Bạn sẽ nhận được bốn đầu vào:
- Số tiền ban đầu (decimal)
- Đơn vị tiền tệ ban đầu (string)
- Số tiền cần cộng (decimal)
- Tỷ giá quy đổi sang EUR (decimal)
In đầu ra theo định dạng sau:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}Ví dụ: nếu đầu vào là 100.00, USD, 50.00 và 0.85, đầu ra sẽ là:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURHãy để ý rằng đối tượng Money ban đầu vẫn hiển thị 100.00 USD ngay cả sau khi gọi Add(). Đó chính là tính bất biến! Phương thức Add() đã tạo một đối tượng hoàn toàn mới với giá trị 150.00 USD, còn đối tượng ban đầu vẫn nguyên vẹn. Việc quy đổi được thực hiện trên kết quả của thao tác cộng.
Tự làm thử
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Đọc dữ liệu đầu vào
decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
string initialCurrency = Console.ReadLine();
decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
decimal conversionRate = Convert.ToDecimal(Console.ReadLine());
// TODO: Tạo đối tượng Money ban đầu với số tiền và đơn vị tiền tệ ban đầu
// TODO: In ra số tiền ban đầu bằng GetDisplay()
// TODO: Gọi Add() trên đối tượng ban đầu và lưu kết quả vào một biến mới
// TODO: In ra kết quả sau khi cộng bằng GetDisplay()
// TODO: In lại số tiền ban đầu để chứng minh nó không thay đổi
// TODO: Chuyển kết quả sau khi cộng sang EUR bằng tỷ giá chuyển đổi
// TODO: In ra số tiền đã chuyển đổi bằng GetDisplay()
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online