Một số kỹ thuật xây dựng lớp đặc trƣng của C# 1.Thuộc tính (properties)

Một phần của tài liệu bài giảng lập trình hướng đối tượng c (Trang 36)

4.1. Thuộc tính (properties)

Một đặc trưng cốt lõi của lập trình hướng đối tượng là tính đóng gói về dữ liệu. Mỗi đối tượng được sinh ra có thành phần dữ liệu riêng (private) mà chỉ có bản thân chính đối tượng đó có thể truy nhập được (chỉ có thể truy cập bởi phương thức của chính đối tượng đó). Bên ngoài chỉ có thể truy nhập được nếu như bản thân đối tượng đó chủ động cung cấp các dịch vụ cho phép truy nhập vào thành phần dữ liệu của mình. Cụ thể là các đối tượng đó có các phương thức cho phép đọc hay ghi vào các thành phần dữ liệu của chúng.

Tính đóng gói thể hiện khi định nghĩa lớp, các thành phần của lớp được chỉ định phạm vi có thể truy xuất được (Access modifier) bởi các từ khóa public, private, protected… Có hai vấn đề được đặt ra là:

Một là, đối với các thành phần public, ta có thể truy nhập, thay đổi giá trị từ bên ngoài bằng cách gán trực tiếp giá trị cho thành phần đó. Vậy làm thế nào có thể kiểm soát được việc thay đổi giá trị cho thành phần đó có phù hợp hay không? Chẳng hạn, kiểm soát không cho phép gán năm sản xuất của chiếc xe nhận giá trị là một chuỗi ký tự.

Hai là, làm thế nào từ bên ngoài có thể truy xuất được các thành phần riêng (private) của đối tượng?

Trong lý thuyết về lập trình hướng đối tượng nói cung cũng như trong các ngôn ngữ lập trình hướng đối tượng khác như Java hoặc C++ thường không có sự phân biệt giữa hai khái niệm biến thành phần (member variables) hay trường (fields) của lớp với thuộc tính (properties) của lớp, và để giải quyết hai vấn đề trên, khi xây dựng lớp thường định nghĩa các phương thức cho phép thiết lập (setter method) và truy xuất (getter method) thành phần dữ liệu của lớp.

Nhưng Trong C# đưa ra khái niệm thuộc tính (properties) của lớp, phân biệt với khái niệm biến thành phần (member variables) hay trường (fields) của lớp. Thuộc tính cung cấp giao diện cho phép truy cập vào các biến thành phần của lớp thay vì truy cập trực tiếp. Thông qua đó, thuộc tính cung cấp khả năng bảo vệ các trường dữ liệu bên trong lớp bằng việc đọc ghi chúng thông qua thuộc tính. Như vậy, có thể nói thuộc tính trong C# và các phương thức thiết lập và truy xuất trong các ngôn ngữ lập trình khác có cùng bản chất, chỉ khác nhau về cú pháp thể hiện.

Để khai báo thuộc tính, đầu tiên là khai báo tên thuộc tính để truy cập, tiếp theo là phần thân định nghĩa thuộc tính nằm trong cập dấu ({}). Bên trong thân của thuộc tính là khai báo hai bộ truy xuất (get) và thiết lập (set) dữ liệu theo cú pháp:

<tính chất> <kiểu dữ liệu> <Tên thuộc tính> {

get

{

//thân bộ truy xuất } set { //thân bộ thiết lập } }

Ví dụ (III.6) Thuộc tính của lớp

Bước 1: Vẫn trong solution Session_III, thêm một dự án mới tên là Properties Bước 2: Thêm lớp Employer vào dự án và viết mã như bên dưới

class Employer {

private string empName; public string EmployerName { get { return empName; } set { empName = value; } } }

Bước 3: Chèn mã vào lớp Program như sau class Program

{

static void Main(string[] args) {

Employer empObj = new Employer(); empObj.EmployerName = "John Carter";

Console.WriteLine("The name: " + empObj.EmployerName); Console.ReadLine();

} } }

Bước 4: Thiết lập Properites là dự án mặc định bằng cách click chuột phải chọn Set as Startup Project

Bước 5: Dịch và chạy chương trình màn hình hiển thị như sau

Hình (III.3) Màn hình hiển thị Ví dụ (III.6)

Trong Ví dụ (III.6) khi định nghĩa lớp Employer đã định nghĩa thuộc tính EmployerName bao gồm bộ truy xuất (get) để đọc trường empName, bộ thiết lập (set) để ghi dữ liệu vào trường empName của lớp. Trong hai bộ này có thể viết thêm các lệnh xử lý khác tuy theo yêu cầu được đặt ra chẳng hạn như kiểm soát giá trị thiết lập cho các trường của lớp.

Bộ truy xuất dữ liệu (get accessor)

Phần khai báo bộ truy xuất dữ liệu tương tự như một phương thức của lớp dùng để trả về giá trị có kiểu dữ liệu chính là kiểu của biến thành phần cần truy xuất. Trong ví dụ trên, bộ truy

xuất dữ liệu của thuộc tính EmployerName cũng tương tự như một phương thức trả về một giá trị chuỗi chính là giá trị của biến thành viên empName của đối tượng hiện thời:

get {

return empName; }

Trong ví dụ này, một biến thành viên cục bộ được trả về thông qua lệnh return. Ngoài ra cũng thể thực hiện các câu lệnh khác nữa để tính toán xử lý rồi trả về giá trị tùy theo yêu cầu Bất cứ khi nào ta tham chiếu đến một thuộc tính hay là gán giá trị thuộc tính cho một biến khác thì bộ truy xuất dữ liệu sẽ được thực hiện để đọc giá trị của biến thành phần:

Ví dụ:

Console.WriteLine("The name: " + empObj.EmployerName);

Bộ truy xuất lấy dữ liệu thực hiện và giá trị của thuộc tính được trả về chính là giá trị của biến thành viên empName của đối tượng hiện thời.

Bộ thiết lập dữ liệu (set accessor)

Bộ thiết lập dữ liệu sẽ thiết lập một giá trị mới cho biến thành phần và tương tự như một phương thức không có giá trị trả về void. Khi định nghĩa bộ thiết lập dữ lịêu chúng ta phải sử dụng từ khóa value để đại diện cho tham số được truyền vào cho thuộc tính.

set {

empName = value; }

Bộ thiết lập giá trị được thực hiện khi có lệnh gán giá trị cho thuộc tính bằng giá trị truyền vào. Lợi ích của hướng tiếp cận này cho phép các thành phần bên ngoài có thể tương tác với thuộc tính một cách trực tiếp, mà vẫn đảm bảo việc che dấu dữ lịêu cũng như đặc tính đóng gói dữ lịêu trong thiết kế hướng đối tượng.

Ví dụ:

empObj.EmployerName = "John Carter";

//value chính là “John Carter”

Trong trường hợp này bộ thiết lập thuộc tính thiết lập giá trị cho biến thành viên. Ngoài ra, ta có thể viết thêm các lệnh trong bộ thiết lập cho phép kiểm soát giá trị truyền vào cho thuộc tính có hợp quy cách hay không hoặc lưu trữ giá trị của thuộc tính vào cơ sở dữ liệu nếu cần thiết.

Hai bộ truy xuất và thiết lập thuộc tính tạo nên 3 loại thuộc tính khác nhau. Nếu thuộc tính chỉ được định nghĩa bộ truy xuất ta nói thuộc tính đó là thuộc tính chỉ đọc. Nếu chỉ có bộ thiết lập ta nói đó là thuộc tính chỉ ghi. Nếu thuộc tính có cả hai bộ truy xuất và thiết lập ta nói đó là bộ truy xuất đọc/ghi.

Hình (III.4) Ba loại thuộc tính

Từ C# 3.0 trở đi cho phép định nghĩa thuộc tính ngắn gọn hơn. Cách viết này gọi là định nghĩa thuộc tính tự động. Trong Ví dụ (III.6) thay vì định nghĩa thuộc tính EmployerName tường minh như trên, ta có thể định nghĩa ngắn gọn như sau

class Employer {

public string EmployerName { get; set; }

}

Theo cách định nghĩa này, chương trình dịch sẽ tự động tạo ra biến thành phần private và chỉ được truy xuất hoặc thiết lập thông qua thuộc tính EmployerName này. Ta có thể thay thế lớp Employer với mã như trên rồi dịch và chạy lại chương trình ở Ví dụ (III.6), kết quả vẫn không thay đổi.

4.2. Chỉ mục

Trong phần trên đã trình bày cách định nghĩa các thuộc tính cho phép đọc ghi giá trị của các biến thành phần của lớp. Bên ngoài có thể đọc ghi các biến thành phần thông qua các thuộc tính này.

Ta xem xét thêm một trường hợp nếu biến thành phần của lớp là một mảng hay tổng quát hơn là một tập hợp các phần tử cùng kiểu thì để đọc ghi giá trị cho từng phần tử ta phải xây dựng thuộc tính hoặc phương thức để đọc ghi giá trị cho từng phần tử. Để đơn giản hóa việc này và cung cấp một cách thức cho phép đọc ghi giá trị cho từng phần tử này giống như đọc ghi giá trị cho từng phần tử của mảng. C# đưa ra khái niệm chỉ mục (indexer)

Cú pháp khai báo chỉ mục trong lớp như sau:

<kiểu trả về> this [<kiểu tham số> <tham số>] {

//định nghĩa bộ truy xuất(get;) //định nghĩa bộ thiết lập (set;) }

Trong đó <kiểu trả về> được quyết định bởi kiểu của biến thành phần được trả về bởi chỉ mục, <tham số> thường là chỉ số của phần tử trong biến thành phần mà chỉ mục tham chiếu

tới nên <kiểu tham số> thường là các kiểu nguyên dùng để truy nhập vào từng phần tử của biến thành phần của lớp là mảng hay tập Hợp

Ví dụ (III.7) Sử dụng chỉ mục

Bước 1: Vẫn trong solution Session_III, thêm một dự án mới tên là Indexer Bước 2: Thêm lớp EmployeeList vào dự án và viết mã như bên dưới

class EmployeeList {

private string[] empNames = new string[2]; public string this[int index]

{ get get { return empNames[index]; } set { empNames[index] = value; } } }

Bước 3: Chèn mã vào lớp Program như sau class Program

{

static void Main(string[] args) {

EmployeeList lstObj = new EmployeeList(); lstObj[0] = "John Carter";

lstObj[1] = "Nicolat Gate";

Console.WriteLine("Employer name: "); for (int i = 0; i < 2; i++)

Console.WriteLine(lstObj[i]); Console.ReadLine();

} } }

Bước 4: Thiết lập Indexer là dự án mặc định bằng cách click chuột phải chọn Set as Startup Project

Bước 5: Dịch và chạy chương trình màn hình hiển thị như sau

Trong Error! Reference source not found. lớp EmployeeList có biến thành phần empNames là một mảng 2 phần tử. Hai phần tử này được truy nhập thông qua một chỉ mục được tạo ra với tham số vào cho chỉ mục là chỉ số của phần tử muốn truy nhập. Từ bên ngoài, thông qua chỉ mục này, mỗi phần tử của empNames được truy nhập như một phần tử của mảng bằng cách truyền vào chỉ số

Một phần của tài liệu bài giảng lập trình hướng đối tượng c (Trang 36)