Lập trình hướng đối tượng (Object Oriented Programming – OOP) là phương pháp lập trình cho phép mô phỏng các đối tượng trong thế giới thực vào trong chương trình với những dữ liệu và hành động được chứa trong đối tượng đó. Tuy nhiên, có những dữ liệu và hành vi trong đối tượng cần phải được bảo vệ, được che giấu khỏi những tác động không mong muốn từ bên ngoài. Đặc điểm này là thể hiện tính đóng gói (Encapsulation) của lập trình hướng đối tượng.
Một số người khi mới bắt đầu lập trình hướng đối tượng thường tự hỏi tại sao không làm cho mọi thứ trong đối tượng đó đều công khai (public) ra bên ngoài, khi cần giá trị nào thì gọi được giá trị đó ngay. Chúng ta thử cùng xét ví dụ sau:
using System; namespace Demo { class Student { public int stuId; public string name; } class Test { public static void Main() { Student stu = new Student(); Console.Write("Input Id: "); stu.stuId = Convert.ToInt32(Console.ReadLine()); Console.Write("Input Name: "); stu.name = Console.ReadLine(); Console.WriteLine("Name of stuId: " + stu.stuId + " is: " + stu.name); Console.ReadLine(); } } }
Trong ví dụ trên, chúng ta tạo ra lớp Student dùng để chứa thông tin về mã (stuId) và tên (name) của một sinh viên. Sau đó, trong hàm Main(), chúng ta tạo ra một đối tượng Student, nhập thông tin cho đối tượng này.
Các bạn thấy rằng, lớp Student này của chúng ta được sử dụng bình thường mà không cần quan tâm đến việc cài đặt chỉ định từ truy xuất public, hay private cho các thành viên trong lớp. Chúng ta giả sử rằng tất cả dữ liệu sẽ hợp lệ (ví dụ giá trị của stuId luôn lớn hơn 0).
Tuy nhiên, thực tế lại không giống với sự tưởng tượng của chúng ta. Có thể, khi các bạn đưa chương trình này cho người khác sử dụng thì khi nhập giá trị cho stuId, người ta lại nhập vào một giá trị âm. Và do đó, có thể dẫn đến một loại tính toán sai lầm về sau.
Vì vậy, vấn đề che dấu thông tin trở nên cần thiết để người sử dụng chương trình không thể truy cập trực tiếp vào các thành phần được bảo vệ trong lớp (ví dụ trong trường hợp này của chúng ta là trường stuId). Đây cũng chính là lý do từ khóa private xuất hiện. Và như vậy, chúng ta có thể viết lại ví dụ trên như sau:
using System; namespace Demo { class Student { private int stuId; private string name; } class Test { public static void Main() { Student stu = new Student(); Console.Write("Input Id: "); stu.stuId = Convert.ToInt32(Console.ReadLine()); // lỗi Console.Write("Input Name: "); stu.name = Console.ReadLine(); // lỗi Console.WriteLine("Name of stuId: " + stu.stuId + " is: " + stu.name); Console.ReadLine(); } } }
Nếu như các bạn cố gắng biên dịch đoạn mã trên thì chắc chắn các bạn sẽ gặp lỗi tại nơi truy cập vào các dữ liệu thành viên của đối tượng Student (dòng 17 và dòng 19). Lý do là hai thành phần member: stuId và name đã trở thành dữ liệu riêng, nội bộ của lớp Student, không thể truy cập trực tiếp từ bên ngoài.
Như vậy, làm cách nào để có thể từ bên ngoài đưa dữ liệu vào cho đối tượng của lớp Student?
Cách mà các ngôn ngữ khác như Java, C++ thực hiện là thêm cách phương thức đặc biệt hổ trợ truy cập vào. Những phương thức này có nhiệm vụ thay đổi giá trị được đóng gói bên trong hoặc lấy giá trị đó ra để sử dụng. Tên của các phương thức này bắt đầu bằng set (thiết lập giá trị) hoặc get (lấy giá trị). Ví dụ:
using System; namespace Demo { class Student { private int stuId; private string name; public Student() { stuId = 0; name = string.Empty; } public void setStuId(int stuId) { if (stuId > 0) this.stuId = stuId; else this.stuId = 0; } public int getStuId() { return stuId; } public void setName(string name) { this.name = name; } public string getName() { return name; } } class Test { public static void Main() { Student stu = new Student(); Console.Write("Input Id: "); stu.setStuId(Convert.ToInt32(Console.ReadLine())); Console.Write("Input Name: "); stu.setName(Console.ReadLine()); Console.WriteLine("Name of stuId: " + stu.getStuId() + " is: " + stu.getName()); Console.ReadLine(); } } }
Trong ví dụ trên, chúng ta đã thêm vào lớp Student các phương thức public để truy cập vào dữ liệu. Đồng thời, thay vì truy cập trực tiếp vào các biến thành viên thì bây giờ chúng ta có thể gián tiếp truy cập thông qua các phương thức tương ứng.
Trong phương thức setStuId(), chúng ta có thực hiện việc kiểm tra để đảm bảo giá trị của stuId luôn là số dương.
Trong lập trình hướng đối tượng thì hai phương thức này là đủ để cho chúng ta bảo vệ các dữ liệu nhạy cảm :).
C# đã nâng cấp hai phương thức trên thành một cấu trúc mới, gọi là property. Sử dụng property, chúng ta có thể thao tác với các dữ liệu được bảo vệ trong đối tượng một cách rất tự nhiên giống như là truy cập vào một biến dữ liệu kiểu public.
Cú pháp khai báo property trong C#:
kiểu_trả_về biến; chỉ_định_từ_truy_xuất kiểu_trả_về tên_property { set { biến = value; } get { return biến; } }
Ví dụ:
int stuId; public int StuId { set { stuId = value; } get { return stuId; } }
Như chúng ta thấy, việc khai báo property giống như là khai báo một biến thành viên public. Chỉ khác là chúng ta có thêm hai từ khóa get và set. Từ khóa get sẽ lấy dữ liệu private để đưa cho nơi gọi property này, từ khóa set thì sẽ lấy dữ liệu truyền vào từ bên ngoài.
Ví dụ trên có thể sửa đổi lại bằng việc sử dụng property như sau:
using System; namespace Demo { class Student { private int stuId; public int StuId { get { return stuId; } set { if (value > 0) stuId = value; else stuId = 0; } } private string name; public string Name { get { return name; } set { name = value; } } public Student() { stuId = 0; name = string.Empty; } } class Test { public static void Main() { Student stu = new Student(); Console.Write("Input Id: "); stu.StuId = Convert.ToInt32(Console.ReadLine()); Console.Write("Input Name: "); stu.Name = Console.ReadLine(); Console.WriteLine("Name of stuId: " + stu.StuId + " is: " + stu.Name); Console.ReadLine(); } } }
Chúng ta hãy xem xét hoạt động của property như thế nào với đoạn chương trình sau:
Student stu = new Student(); stu.StuId = 4; stu.Name = "Quang"; Console.WriteLine("Name of stuId: " + stu.StuId + " is: " + stu.Name);
Khi chúng ta gọi stu.Name = “Quang”, có nghĩa là chúng ta đã truyền chuỗi “Quang” vào cho biến name, thông qua property Name. Đoạn mã trong phần set của property Name sẽ được thực thi
name = value;
value là từ khóa của C#, sẽ mang giá trị mà chúng ta truyền vào cho một property (kiểu của value sẽ được hiểu là kiểu của giá trị truyền vào).
Tương tự như vậy khi chúng ta gọi: stu.StuId = 4;
Khi chúng ta gọi:
Console.WriteLine("Name of stuId: " + stu.StuId + " is: " + stu.Name);
thì đoạn code trong phần get của hai property StuId và Name sẽ được gọi để lấy dữ liệu đã được lưu trong các biến thành viên stuId và Name. Trình biên dịch sẽ tự động xử lý để biết được là chúng ta gọi stu.Name để truyền dữ liệu vào hay là lấy dữ liệu ra ngoài.
Như chúng ta thấy, việc sử dụng property giúp cho code của chúng ta tự nhiên hơn và dễ đọc hơn rất nhiều. Giả sử bạn muốn tăng mã của đối tượng Student lên 1 thông qua các phương thức getStuId và setStuId thì bạn phải gọi:
stu.setStuId(stu.getStuId() + 1);
Nhưng nếu sử dụng property StuId, thì chúng ta chỉ cần gọi:
p.StuId++;
Như vậy, chúng ta thấy rằng: sử dụng property trong C# không mang lại lợi ích nào về tốc độ hoạt động của chương trình mà chỉ là một cách giúp cho code của chúng ta dễ đọc và dễ viết hơn, giúp cho việc tăng hiệu suất làm việc.