19/09/2013

Property trong C#

Lập trình hướng đối tượng là phương pháp lập trình nhằm mô tả 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 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.


Sẽ không quá ngạc nhiên với những ai mới bắt đầu lập trình hướng đối tượng khi một ngày nào đó các bạn đang làm và tự hỏi tại sao không làm cho mọi thứ trong đối tượng đó đều public ra 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 Example
{
    class Person
    {
        public string name;
        public int age;
    }
    class Test
    {
        public static void Main()
        {
            Person p = new Person();
            Console.Write("Nhap vao ten: ");
            p.name = Console.ReadLine();
            Console.Write("Nhap vao tuoi: ");
            p.age = Int32.Parse(Console.ReadLine());

            Console.WriteLine(p.name + " is " + p.age);
            Console.ReadLine();
        }
    }
}
Trong ví dụ trên, chúng ta tạo lớp Person dùng để chứa thông tin về tên và tuổi của một người. Sau đó, trong hàm Main, chúng ta sẽ tạo một đối tượng Person, sử dụng các phương thức Console.WriteLine và Console.ReadLine để đưa dữ liệu vào cho đối tượng. Chú ý rằng chúng ta phải sử dụng phương thức Int32.Parse để chuyển chuỗi ta nhập thành số cho phù hợp với kiểu số nguyên lưu trữ tên.
Chúng ta thấy rằng lớp Person này của chúng ta vẫn có được sử dụng bình thường mà không cần phải quan tâm cần đặt đâu là public, đâu là private ở các thành viên trong lớp (đặt tất cả là public). Chúng ta đang giả sử một điều, rằng sẽ không có ai nhập vào các giá trị bất thường (-15, 200 cho tuổi chẳng hạn). Tuy nhiên, cuộc sống không thể theo những gì mà bạn giả sử. Biết đâu, khi đưa chương trình này cho người khác sử dụng thì họ sẽ nhập vào một giá trị nào khác, ví dụ –15 (theo các nghiên cứu  khoa học hiện tại thì chưa có ai có tuổi là –15 ^_^). Và kết quả cuối cùng là chương trình của chúng ta sẽ tính toán dựa trên những dữ kiện sai lầm (mặc dù trong chương trình minh họa này, chúng ta không hề sử dụng Age để tính toán, nhưng trong thực tế sẽ có)
Từ đó, xuất hiện nhu cầu cần phải giấu dữ liệu tuổi đi để người khác không thể truy cập trực tiếp và thay đổi giá trị tuổi được. Đó chính là lý do tại sao từ khóa “private” lại xuất hiện. Khi đó, chúng ta sẽ chỉnh sửa ví dụ trên thành như sau:
using System;
namespace Example
{
    class Person
    {
        private string name; //name không thể được truy cập từ bên ngoài
        private int age; //age không thể được truy cập từ bên ngoài
    }
    class Test
    {
        public static void Main()
        {
            Person p = new Person();
            Console.Write("Nhap vao ten: ");
            p.name = Console.ReadLine(); //lỗi
            Console.Write("Nhap vao tuoi: ");
            p.age = Int32.Parse(Console.ReadLine()); //lỗi

            Console.WriteLine(p.name + " is " + p.age); //lỗi
            Console.ReadLine();
        }
    }
}
Nếu như cố gắng biên dịch đoạn mã này, thì chắc chắn 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 Person. Lý do là hai dữ liệu “name” và “age” đã được chúng ta cho trở thành dữ liệu riêng nội bộ trong lớp Person, không thể truy cập trực tiếp từ bên ngoài vào được. Vậy, làm cách nào để từ bên ngoài có thể đưa dữ liệu vào cho đối tượng của lớp Person. Một cách đơn giản nhất mà các ngôn ngữ hướng đối tượng như Java, C++ vẫn thường sử dụng là thêm các phương thức đặc biệt vào. Những phương thức này có nhiệm vụ là 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 được bắt đầu bằng set (thiết lập giá trị) và get (lấy giá trị). Ví dụ:
using System;
namespace Example
{
    class Person
    {
        private string name;
        private int age;
        public string getName()
        {
            return name;
        }
        public void setName(string name)
        {
            this.name = name;
        }

        public int getAge()
        {
            return age;
        }
        public void setAge(int age)
        {
            if (age < 1)
                this.age = 1;
            else
                this.age = age;
        }
    }
    class Test
    {
        public static void Main()
        {
            Person p = new Person();
            Console.Write("Nhap vao ten: ");
            p.setName(Console.ReadLine());
            Console.Write("Nhap vao tuoi: ");
            p.setAge(Int32.Parse(Console.ReadLine()));
            Console.WriteLine(p.getName() + " is " + p.getAge());
            Console.ReadLine();
        }
    }
}
Các bạn lưu ý những đoạn mã đã có chỉnh sửa được in đậm và in nghiêng. Ở đây, chúng ta đã thêm vào các phương thức public để truy cập vào dữ liệu, đồng thời thay thế việc truy cập trực tiếp vào các biến bằng việc gọi các phương thức tương ứng. Trong phương thức setAge, chúng ta còn thêm một số xử lý để đảm bảo rằng biến age của chúng ta luôn có giá trị từ 1 trở lên (đây là cách để bảo vệ dữ liệu cho các tính toán sau này). Trong lập trình hướng đối tượng thì hai phương thức trên là đủ cho chúng ta bảo vệ các dữ liệu cá nhân. Riêng C# đã nâng cấp hai phương thức trên thành một cấu trúc mới, đó là Property. Sử dụng Property, chúng ta có thể truy cập, thao tác tới các dữ liệu cá nhân trong đối tượng theo một cách rất là tự nhiên như là truy cập vào một biến dữ liệu public. Chúng ta sẽ sửa lại các phương thức setName, setAge và getName, getAge ở trên thành các property như sau:
using System;
namespace Example
{
    class Person
    {
        private string name;
        private int age;
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
        public string Age
        {
            get
            {
                return age;
            }
            set
            {
                if (value < 1)
                    age = 1;
                else
                    age = value;
            }
        }
    }
}
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à như thế, đoạn chương trình trong hàm Main của chúng ta sẽ là:
public static void Main()
{
    Person p = new Person();
    Console.Write("Nhap vao ten: ");
    p.Name = Console.ReadLine());
    Console.Write("Nhap vao tuoi: ");
    p.Age = Int32.parse(Console.ReadLine());
    Console.WriteLine(p.Name + " is " + p.Age);
    Console.ReadLine();
}
Chúng ta hãy xem xét với Property Age như trên thì cách thức hoạt động của chúng là như thế nào với đoạn chương trình:
Person p = new Person();
p.Name = "Tran Xuan Chien";
p.Age = 20;
Console.WriteLine(p.Name + “is ” + p.Age);
Khi chúng ta gọi: p.Name = “Tran Xuan Chien”, có nghĩa là chúng ta truyền chuỗi “Tran Xuan Chien” này vào cho biến name, thông qua Property Name. Và đoạn mã trong phần set của Property Name sẽ được thực thi
name = value;
value là từ khóa mà sẽ mang giá trị chúng ta truyền vào cho một property (kiểu dữ liệu của value được hiểu như là kiểu dữ liệu của giá trị được truyền vào) – chuỗi “Tran Xuan Chien”. Và biến thành viên name đã được gán giá trị tương ứng. Tương tự, khi chúng ta gọi: p.Age = 20 thì đoạn mã trong phần set của Property Age sẽ được thực thi
if (value < 1)
    age = 1;
else
    age = value;
Chúng ta xem giá trị truyền vào có nhỏ hơn 1 hay không. Nếu nhỏ hơn 1 thì mặc định cho age = 1, còn ngược lại thì gán age là giá trị được truyền vào, ở đây là giá trị 20.
Cũng như vậy, khi chúng ta gọi:
Console.WriteLine(p.Name + "is " + p.Age);
thì đoạn code trong phần get của hai Property Name và Age sẽ được gọi để lấy dữ liệu đã được lưu trong các biến thành viên name và age. Trình biên dịch sẽ tự động xử lý để biết được là chúng ta gọi p.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 tuổi của đối tượng Person lên 1 thông qua các phương thức getAge và setAge thì bạn phải gọi:
p.setAge(p.getAge() + 1);
Trong khi nếu như sử dụng Property Age, thì chúng ta chỉ cần gọi:
p.Age++;
Kết luận: 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 (Property khi sau quá trình biên dịch sẽ có những đoạn mã giống như là khi viết các phương thức setValue và getValue), nhưng nó thật sự hữu ích, giúp tăng hiệu suất làm việc. Property trong C# còn có một vài tính năng rất hay và hữu ích khi lập trình. Chúng ta sẽ cùng xem xét trong các một số bài viết tiếp theo.

No comments:

Post a Comment