Triển khai ẩn dữ liệu
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 33 trên 70.
Ẩn dữ liệu không chỉ dừng lại ở việc dùng các trường private. Đó là việc thiết kế lớp sao cho các chi tiết triển khai bên trong hoàn toàn ẩn với code bên ngoài, chỉ để lộ những gì cần thiết thông qua một giao diện công khai rõ ràng.
Hãy xem một lớp theo dõi mật khẩu của người dùng. Bạn không muốn ai truy cập mật khẩu thô, kể cả chỉ để đọc:
public class UserAccount
{
private string passwordHash;
public void SetPassword(string password)
{
passwordHash = HashPassword(password);
}
public bool VerifyPassword(string password)
{
return HashPassword(password) == passwordHash;
}
private string HashPassword(string password)
{
return password.GetHashCode().ToString();
}
}Lưu ý rằng không có cách nào để lấy lại mật khẩu hoặc thậm chí là mã băm. Code bên ngoài chỉ có thể đặt mật khẩu hoặc xác minh mật khẩu, cơ chế lưu trữ bên trong hoàn toàn bị ẩn. Phương thức HashPassword cũng là phương thức riêng tư vì đây là chi tiết triển khai có thể thay đổi mà không ảnh hưởng đến cách sử dụng lớp.
Cách làm này bảo vệ lớp của bạn khỏi bị sử dụng sai mục đích và giúp bạn tự do thay đổi phần bên trong sau này. Nếu bạn quyết định dùng thuật toán băm khác, không có code bên ngoài nào bị hỏng vì không có gì phụ thuộc vào cách lưu mật khẩu.
Thử thách
DễHãy xây dựng một hệ thống ví bảo mật để minh họa việc ẩn dữ liệu thực sự. Bạn sẽ tạo một ví điện tử có số dư được bảo vệ hoàn toàn khỏi truy cập trực tiếp. Code bên ngoài chỉ có thể nạp tiền, rút tiền và kiểm tra xem có thể thực hiện giao dịch hay không, chứ không bao giờ có thể xem hoặc thao tác trực tiếp với số dư thực tế.
Bạn sẽ sắp xếp code trong hai tệp:
Wallet.cs: Tạo lớpWallettrong không gian tênFinance, lớp này giữ số dư được ẩn hoàn toàn. Ví cần có:- Một trường private để lưu số dư (số thập phân)
- Một phương thức private
FormatBalance()trả về số dư được định dạng thành chuỗi tiền tệ như"$123.45", đây là hàm trợ giúp nội bộ không nên được công khai - Một phương thức public
Deposit(decimal amount)chỉ cộng vào số dư nếu số tiền dương, trả vềtruenếu thành công, nếu không thì trả vềfalse - Một phương thức public
Withdraw(decimal amount)chỉ trừ vào số dư nếu số tiền dương và không vượt quá số dư hiện tại, trả vềtruenếu thành công, nếu không thì trả vềfalse - Một phương thức public
CanAfford(decimal amount)trả vềtruenếu số dư lớn hơn hoặc bằng số tiền, nếu không thì trả vềfalse - Một phương thức public
GetStatement()sử dụng phương thức privateFormatBalance()bên trong và trả về"Current balance: {formatted balance}"
Program.cs: Trong tệp chính, hãy tạo mộtWalletvà thực hiện một loạt giao dịch bằng các giá trị đầu vào. Cho thấy cách ví bảo vệ trạng thái nội bộ mà vẫn cung cấp chức năng hữu ích thông qua giao diện public.
Bạn sẽ nhận ba giá trị đầu vào:
- Số tiền gửi ban đầu (số thập phân)
- Số tiền muốn rút (số thập phân)
- Số tiền cần kiểm tra khả năng chi trả (số thập phân)
In đầu ra theo định dạng này:
Deposit {amount}: {True/False}
{GetStatement() result}
Withdraw {amount}: {True/False}
{GetStatement() result}
Can afford {amount}: {True/False}Ví dụ, nếu các giá trị đầu vào là 100.50, 30.25 và 80.00, đầu ra sẽ là:
Deposit 100.50: True
Current balance: $100.50
Withdraw 30.25: True
Current balance: $70.25
Can afford 80.00: FalseĐiểm cốt yếu là code bên ngoài không bao giờ trực tiếp truy cập trường số dư. Ngay cả chuỗi số dư đã định dạng cũng được lấy thông qua một phương thức public, phương thức này sử dụng một hàm trợ giúp private bên trong. Đây là cách ẩn dữ liệu: chi tiết triển khai hoàn toàn không hiển thị, và bạn có thể tự do thay đổi cách lưu trữ hoặc định dạng số dư mà không làm hỏng bất kỳ code nào đang dùng ví của bạn!
Tự làm thử
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Đọc các giá trị đầu vào
decimal depositAmount = Convert.ToDecimal(Console.ReadLine());
decimal withdrawAmount = Convert.ToDecimal(Console.ReadLine());
decimal checkAmount = Convert.ToDecimal(Console.ReadLine());
// TODO: Tạo một instance của Wallet
// TODO: Nạp tiền và in kết quả theo định dạng: "Deposit {amount}: {True/False}"
// TODO: In sao kê bằng GetStatement()
// TODO: Rút tiền và in kết quả theo định dạng: "Withdraw {amount}: {True/False}"
// TODO: In sao kê bằng GetStatement()
// TODO: Kiểm tra số dư có đủ hay không và in kết quả theo định dạng: "Can afford {amount}: {True/False}"
}
}
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