Nạp chồng phương thức
Một ký hiệu (signature) của một phương thức được định nghĩa như tên của phương thức cùng với danh sách tham số của phương thức. Hai phương thức khác nhau khi ký hiệu của chúng khác là khác nhau tức là khác nhau khi tên phương thức khác nhau hay danh sách ...
Một ký hiệu (signature) của một phương thức được định nghĩa như tên của phương thức cùng với danh sách tham số của phương thức. Hai phương thức khác nhau khi ký hiệu của chúng khác là khác nhau tức là khác nhau khi tên phương thức khác nhau hay danh sách tham số khác nhau. Danh sách tham số được xem là khác nhau bởi số lượng các tham số hoặc là kiểu dữ liệu của tham số. Ví dụ đoạn mã sau, phương thức thứ nhất khác phương thức thứ hai do số lượng tham số khác nhau. Phương thức thứ hai khác phương thức thứ ba do kiểu dữ liệu tham số khác nhau:
void myMethod( int p1 );
void myMethod( int p1, int p2 );
void myMethod( int p1, string p2 );
Một lớp có thể có bất cứ số lượng phương thức nào, nhưng mỗi phương thức trong lớp phải có ký hiệu khác với tất cả các phương thức thành viên còn lại của lớp.
Ví dụ sau minh họa lớp Time có hai phương thức khởi dựng, một phương thức nhận tham số là một đối tượng DateTime còn phương thức thứ hai thì nhận sáu tham số nguyên.
Minh họa nạp chồng phương thức khởi dựng.
-----------------------------------------------------------------------------
using System;
public class Time
{
public void DisplayCurrentTime()
{
Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", Date, Month, Year, Hour, Minute, Second);
}
public Time( System.DateTime dt)
{
Year = dt.Year;
Month = dt.Month;
Date = dt.Day;
Hour = dt.Hour;
Minute = dt.Minute;
Second = dt.Second;
}
public Time(int Year, int Month, int Date, int Hour, int Minute, int Second)
{
this.Year = Year;
this.Month = Month;
this.Date = Date;
this.Hour = Hour;
this.Minute = Minute;
this.Second = Second;
}
private int Month;
private int Date;
private int Hour;
private int Minute;
private int Second;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now;
Time t1 = new Time( currentTime);
t1.DisplayCurrentTime();
Time t2 = new Time(2002,6,8,18,15,20);
t2.DisplayCurrentTime();
}
}
-----------------------------------------------------------------------------
Kết quả:
2/1/2002 17:50:17
8/6/2002 18:15:20
-----------------------------------------------------------------------------
Như chúng ta thấy, lớp Time trong ví dụ minh họa 4.9 có hai phương thức khởi dựng. Nếu hai phương thức có cùng ký hiệu thì trình biên dịch sẽ không thể biết được gọi phương thức nào khi khởi tạo hai đối tượng là t1 và t2. Tuy nhiên, ký hiệu của hai phương thức này khác nhau vì tham số truyền vào khác nhau, do đó trình biên dịch sẽ xác định được phương thức nào được gọi dựa vào các tham số.
Khi thực hiện nạp chồng một phương thức, bắt buộc chúng ta phải thay đổi ký hiệu của phương thức, số tham số, hay kiểu dữ liệu của tham số. Chúng ta cũng có thể toàn quyền thay đổi giá trị trả về, nhưng đây là tùy chọn. Nếu chỉ thay đổi giá trị trả về thì không phải nạp chồng phương thức mà khi đó hai phương thức khác nhau, và nếu tạo ra hai phương thức cùng ký hiệu nhưng khác nhau kiểu giá trị trả về sẽ tạo ra một lỗi biên dịch.
.
-----------------------------------------------------------------------------
using System;
public class Tester
{
private int Triple( int val)
{
return 3*val;
}
private long Triple(long val)
{
return 3*val;
}
public void Test()
{
int x = 5;
int y = Triple(x);
Console.WriteLine("x: {0} y: {1}", x, y);
long lx = 10;
long ly = Triple(lx);
Console.WriteLine("lx: {0} ly:{1}", lx, ly);
}
static void Main()
{
Tester t = new Tester();
t.Test();
}
}
-----------------------------------------------------------------------------
Kết quả: x: 5 y: 15 lx: 10 ly:30
-----------------------------------------------------------------------------
Trong ví dụ này, lớp Tester nạp chồng hai phương thức Triple(), một phương thức nhận tham số nguyên int, phương thức còn lại nhận tham số là số nguyên long. Kiểu giá trị trả về của hai phương thức khác nhau, mặc dù điều này không đòi hỏi nhưng rất thích hợp trong trường hợp này.
Thuộc tính là khái niệm cho phép truy cập trạng thái của lớp thay vì thông qua truy cập trực tiếp các biến thành viên, nó sẽ đựơc thay thế bằng việc thực thi truy cập thông qua phương thức của lớp.
Đây thật sự là một điều lý tưởng. Các thành phần bên ngoài (client) muốn truy cập trạng thái của một đối tượng và không muốn làm việc với những phương thức. Tuy nhiên, người thiết kế lớp muốn dấu trạng thái bên trong của lớp mà anh ta xây dựng, và cung cấp một cách gián tiếp thông qua một phương thức.
Thuộc tính là một đặc tính mới được giới thiệu trong ngôn ngữ C#. Đặc tính này cung cấp khả năng bảo vệ các trường dữ liệu bên trong một lớp bằng việc đọc và viết chúng thông qua thuộc tính. Trong ngôn ngữ khác, điều này có thể được thực hiện thông qua việc tạo các phương thức lấy dữ liệu (getter method) và phương thức thiết lập dữ liệu (setter method).
Thuộc tính được thiết kế nhắm vào hai mục đích: cung cấp một giao diện đơn cho phép truy cập các biến thành viên, Tuy nhiên cách thức thực thi truy cập giống như phương thức trong đó các dữ liệu được che dấu, đảm bảo cho yêu cầu thiết kế hướng đối tượng. Để hiểu rõ đặc tính này ta sẽ xem ví dụ bên dưới:
Sử dụng thuộc tính.
-----------------------------------------------------------------------------
using System; public class Time { public void DisplayCurrentTime() { Console.WriteLine("Time : {0}/{1}/{2} {3}:{4}:{5}", date, month, year, hour, minute, second); } public Time( System.DateTime dt) { year = dt.Year; month = dt.Month; date = dt.Day; hour = dt.Hour; minute = dt.Minute; second = dt.Second; } Public int Hour; { get { return hour; } set { hour=value; } } //Biến thành viên private private int year; private int month; private int date; private int hour; private int minute; private int second; } public class Tester { static void Main() { System.DateTime currentTime = System.DateTime.Now; Time t = new Time( currentTime ); t.DisplayCurrentTime(); // Lấy dữ liệu từ thuộc tính Hour int theHour = t.Hour; Console.WriteLine(" Retrieved the hour: {0}", theHour); theHour++; t.Hour = theHour; Console.WriteLine("Updated the hour: {0}", theHour); } }
-----------------------------------------------------------------------------
Kết quả:
Time : 2/1/2003 17:55:1
Retrieved the hour: 17
Updated the hour: 18
-----------------------------------------------------------------------------
Để 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 cập lấy và thiết lập dữ liệu:
public int Hour { get { return hour; } set { hour=value; } }
Mỗi bộ truy cập được khai báo riêng biệt để làm hai công việc khác nhau là lấy hay thiết lập giá trị cho thuộc tính. Giá trị thuộc tính có thể được lưu trong cơ sở dữ liệu, khi đó trong phần thân của bộ truy cập sẽ thực hiện các công việc tương tác với cơ sở dữ lịêu. Hoặc là giá trị thuộc tính được lưu trữ trong các biến thành viên của lớp như trong ví dụ:
private int hour;
Truy cập lấy dữ liệu (get accessor)
Phần khai báo tương tự như một phương thức của lớp dùng để trả về một đối tượng có kiểu dữ liệu của thuộc tính. Trong ví dụ trên, bộ truy cập lấy dữ liệu get của thuộc tính Hour cũng tương tự như một phương thức trả về một giá trị int. Nó trả về giá trị của biến thành viên hour nơi mà giá trị của thuộc tính Hour lưu trữ:
get { return hour; }
Trong ví dụ này, một biến thành viên cục bộ được trả về, nhưng nó cũng có thể truy cập dễ dàng một giá trị nguyên từ cơ sở dữ lịêu, hay thực hiện việc tính toán tùy ý.
Bất cứ khi nào chúng 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 thì bộ truy cập lấy dữ liệu get sẽ được thực hiện để đọc giá trị của thuộc tính:
Time t = new Time( currentTime );
int theHour = t.Hour;
Khi lệnh thứ hai được thực hiện thì giá trị của thuộc tính sẽ được trả về, tức là bộ truy cập lấy dữ lịêu get sẽ được thực hiện và kết quả là giá trị của thuộc tính được gán cho biến cục bộ theHour.
Bộ truy cập thiết lập dữ liệu ( set accessor)
Bộ truy cập này sẽ thiết lập một giá trị mới cho thuộc tính và tương tự như một phương thức trả về một giá trị void. Khi định nghĩa bộ truy cập 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 và được lưu trữ bởi thuộc tính:
set { hour = value; }
Như đã nói trước, do ta đang khai báo thuộc tính lưu trữ dưới dạng biến thành viên nên trong phần thân của bộ truy cập ta chỉ sử dụng biến thành viên mà thôi. Bộ truy cập thiết lập hoàn toàn cho phép chúng ta có thể viết giá trị vào trong cơ sở dữ lịêu hay cập nhật bất cứ biến thành viên nào khác của lớp nếu cần thiết.
Khi chúng ta gán một giá trị cho thuộc tính thì bộ truy cập thiết lập dữ liệu set sẽ được tự động thực hiện và một tham số ngầm định sẽ được tạo ra để lưu giá trị mà ta muốn gán:
theHour++;
t.Hour = theHour;
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 (client) có thể tương tác với thuộc tính một cách trực tiếp, mà không phải hy sinh 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.
Giả sử chúng ta muốn tạo một phiên bản khác cho lớp Time cung cấp một số giá trị static để hiển thị ngày và giờ hiện hành. Ví dụ sau minh họa cho cách tiếp cận này.
Sử dụng thuộc tính hằng static.
-----------------------------------------------------------------------------
using System;
public class RightNow
{
// Định nghĩa bộ khởi tạo static cho các biến
static static RightNow()
{
System.DateTime dt = System.DateTime.Now;
Year = dt.Year;
Month = dt.Month;
Date = dt.Day;
Hour = dt.Hour;
Minute = dt.Minute;
Second = dt.Second;
}
// Biến thành viên static
public static int Year;
public static int Month;
public static int Date;
public static int Hour;
public static int Minute;
public static int Second;
}
public class Tester
{
static void Main()
{
Console.WriteLine("This year: {0}", RightNow.Year.ToString());
RightNow.Year = 2003;
Console.WriteLine("This year: {0}",
RightNow.Year.ToString());
}
}
-----------------------------------------------------------------------------
Kết quả:
This year: 2002
This year: 2003
-----------------------------------------------------------------------------
Đoạn chương trình trên hoạt động tốt, tuy nhiên cho đến khi có một ai đó thay đổi giá trị của biến thành viên này. Như ta thấy, biến thành Year trên đã được thay đổi đến 2003. Điều này thực sự không như mong muốn của chúng ta.
Chúng ta muốn đánh dấu các thuộc tính tĩnh này không được thay đổi. Nhưng khai báo hằng cũng không được vì biến tĩnh không được khởi tạo cho đến khi phương thức khởi dựng static được thi hành. Do vậy C# cung cấp thêm từ khóa readonly phục vụ chính xác cho mục đich trên. Với ví dụ trên ta có cách khai báo lại như sau:
public static readonly int Year;
public static readonly int Month;
public static readonly int Date;
public static readonly int Hour;
public static readonly int Minute;
public static readonly int Second;
Khi đó ta phải bỏ lệnh gán biến thành viên Year, vì nếu không sẽ bị báo lỗi:
// RightNow.Year = 2003; // error
Chương trình sau khi biên dịch và thực hiện như mục đích của chúng ta.
Câu hỏi và trả lời
Có phải chúng ta chỉ nên sử dụng lớp với các dữ liệu thành viên?
Nói chung là chúng ta không nên sử dụng lớp chỉ với dữ liệu thành viên. Ý nghĩa của môt lớp hay của lập trình hướng đối tượng là khả năng đóng gói các chức năng và dữ liệu vào trong một gói đơn.
Có phải tất cả những dữ liệu thành viên luôn luôn được khai báo là public để bên ngoài có thể truy cập chúng?
Nói chung là không. Do vấn đề che dấu dữ liệu trong lập trình hướng đối tượng,xu hướng là dữ liệu bên trong chỉ nên dùng cho các phương thức thành viên. Tuy nhiên, như chúng ta đã biết khái niệm thuộc tính cho phép các biến thành viên được truy cập từ bên ngoài thông qua hình thức như là phương thức.
Có phải có rất nhiều lớp được xây dựng sẵn và tôi có thể tìm chúng ở đâu?
Microsoft cung cấp rất nhiều các lớp gọi là các lớp cơ sở .NET. Những lớp này được tổ chức bên trong các namespace. Chúng ta có thể tìm tài liệu về các lớp này trong thư viện trực tuyến của Microsoft. Và một số lớp thường sử dụng cũng được trình bày lần lượt trong các ví dụ của giáo trình này.
Sự khác nhau giữa tham số (parameter) và đối mục (argument)?
Tham số được định nghĩa là những thứ được truyền vào trong một phương thức. Một tham số xuất hiện với định nghĩa của phương thức ở đầu phương thức. Một đối mục là giá trị được truyền vào phương thức. Chúng ta truyền những đối mục vào phương thức phù hợp với những tham số đã khai báo của phương thức.
Chúng ta có thể tạo phương thức bên ngoài của lớp hay không?
Mặc dù trong những ngôn ngữ khác, chúng ta có thể tạo các phương thức bên ngoài của lớp. Nhưng trong C# thì không, C# là hướng đối tượng, do vậy tất cả các mã nguồn phải được đặt bên trong một lớp.
Có phải những phương thức và lớp trong C# hoạt động tương tự như trong các ngôn ngữ khác như C++ hay Java?
Trong hầu hết các phần thì chúng tương tự như nhau. Tuy nhiên, mỗi ngôn ngữ cũng có những khác biệt riêng. Một ví dụ sự khác nhau là C# không cho phép tham số mặc định bên trong một phương thức. Trong ngôn ngữ C++ thì chúng ta có thể khai báo các tham số mặc định lúc định nghĩa phương thức và khi gọi phương thức thì có thể không cần truyền giá trị vào, phương thức sẽ dùng giá trị mặc định. Trong C# thì không được phép. Nói chung là còn nhiều sự khác nhau nữa, nhưng xin dành cho bạn đọc tự tìm hiểu.
Phương thức tĩnh có thể truy cập được thành viên nào và không truy cập được thành viên nào trong một lớp?
Phương thức tĩnh chỉ truy cập được các thành viên tĩnh trong một lớp.
Câu hỏi thêm
Sự khác nhau giữa thành viên được khai báo là public và các thành viên không được khai báo là public?
Từ khoá nào được sử dụng trong việc thực thi thuộc tính của lớp?
Những kiểu dữ liệu nào được trả về từ phương thức?
Sự khác nhau giữa truyền biến tham chiếu và truyền biến tham trị vào một phương thức?
Làm sao truyền tham chiếu với biến kiểu giá trị vào trong một phương thức?
Khi nào thì phương thức khởi dựng được gọi? Câuhỏi 7: Phương thức khởi dựng tĩnh được gọi khi nào?
Có thể truyền biến chưa khởi tạo vào một hàm được không? Câuhỏi 9: Sự khác nhau giữa một lớp và một đối tượng của lớp?
Thành viên nào trong một lớp có thể được truy cập mà không phải tạo thể hiện của lớp?
Lớp mà chúng ta xây dựng thuộc kiểu dữ liệu nào? Câuhỏi 12: Từ khóa this được dùng làm gì trong một lớp?
Bài tập
Xây dựng một lớp đường tròn lưu giữ bán kính và tâm của đường tròn. Tạo các phương thức để tính chu vi, diện tích của đường tròn.
Thêm thuộc tính BanKinh vào lớp được tạo ra từ bài tập 1.
Tạo ra một lớp lưu trữ giá trị nguyên tên myNumber. Tạo thuộc tính cho thành viên này. Khi số được lưu trữ thì nhân cho 100. Và khi số được truy cập thì chia cho 100.
Chương trình sau có lỗi. Hãy sửa lỗi của chương trình và biên dịch chương trình. Dòng lệnh nào gây ra lỗi?
-----------------------------------------------------------------------------
using System; using System.Console; class VD1 { public string first; } class Tester { public static void Main() { VD1 vd = new VD1(); Write("Nhap vao mot chuoi: "); vd.first = ReadLine(); Write("Chuoi nhap vao: {0}", vd.first); } }
-----------------------------------------------------------------------------
Chương trình sau có lỗi. Hãy sửa lỗi của chương trình và biên dịch chương trình. Dòng lệnh nào gây ra lỗi?
-----------------------------------------------------------------------------
class Class1 { public static void GetNumber(ref int x, ref int y) { x = 5; y = 10; } public static void Main() { int a = 0, b = 0; GetNumber(a, b); System.Console.WriteLine("a = {0} b = {1}", a, b); } }
-----------------------------------------------------------------------------
Chương trình sau đây có lỗi. Hãy sửa lỗi và cho biết lệnh nào phát sinh lỗi?
-----------------------------------------------------------------------------
Class Tester
{
public static void Main()
{
Display();
}
public static void Display()
{
System.Console.WriteLine("Hello!");
return 0;
}
}
-----------------------------------------------------------------------------
Viết lớp giải phương trình bậc hai. Lớp này có các thuộc tính a, b, c và nghiệm x1, x2. Hãy xây dựng theo hướng đối tượng lớp trên. Lớp cho phép bên ngoài xem được các nghiệm của phương trình và cho phép thiết lập hay xem các giá trị a, b, c.