Điều quan trọng là bạn phải nhận thức đợc rõ ràng những vai trò riêng của các lớp và các khách hàng. Các lớp có những quyền hạn (rights) xác định, cũng nh các khách hàng. Hình 5.2 liệt kê một vài quyền hạn hữu ích của chúng.
Giả sử có lớp Timevới giao diện nh trên, một biến dawn có thể đợc mô tả nh sau:
Time dawn;
Mô tả này thiết lập một biến dawn nh là thùng chứa (container) cho các đối tợng của lớp Time. Nh chúng ta đã biết, không có đối tợng nào đợc kiến tạo bởi mô tả này. Để cấp phát (allocate) một đôí tợng thực sự, bạn phải sử dụng từ khóa new để kích hoạt một phơng thức kiến tạo (contructor) cho lớp Time:
Quyền hạn của khách hàng Quyền hạn của lớp
Mô tả các biến của kiểu lớp. Định nghĩa giao diện chung cho lớp. Tạo các thể hiện của lớp sử dụng các ph-
ơng thức kiến tạo lớp. Che giấu mọi chi tiết của sự thực thi đốivới các khách hàng. Gửi thông điệp tới các thể hiện của lớp
bằng cách kích hoạt các phơng thức thể hiện đợc định nghĩa trong lớp.
Bảo vệ các dữ liệu “nội bộ” (internal data) khỏi sự truy cập từ các khách hàng. Biết giao diện chung của lớp (tên các ph-
ơng thức thể hiện, số và kiểu các tham số, các kiểu giá trị trả về.)
Thay đổi chi tiết của việc thực thi mọi lúc, miễn là những phần còn lại của giao diện chung không bị ảnh hởng.
Biết đợc các phơng thức thể hiện nào sửa đổi (alter, mutate) thể hiện.
Hình 5.3: Quyền hạn của khách hàng và lớp
dawn = new Time (5,35)
Điều lệnh này kích hoạt phơng thức kiến tạo Time, tạo một hộp cho một đối tợng kiểu Time và điền vào chúng giá trị 5:35 A.M.
Câu lệnh gán khiến cho biến dawn chứa đối tợng mới đợc tạo.
Trên thực tế, có một sự khác biệt quan trọng giữa các đối tợng và các gía trị đơn giản nh các số nguyên. Các biến kiểu int hay double chứa các số nguyên hay dấu phẩy động. Các biến kiểu lớp nh Time chỉ chứa tham chiếu (references) tới các đối tợng; còn bản thân đối tợng thì đợc định vị ở một nơi khác (gọi là heap).
Có thể viết các điều lệnh mô tả và kiến tạo trên cùng một dòng:
Time dawn = new Time (5,35);
Mô tả một biến của một lớp đợc định nghĩa bởi ngời sử dụng chỉ đơn thuần xác định một tham chiếu tới một đối tợng kiểu lớp đó chứ không kiến tạo hay khởi tạo đối tợng. Các đối tợng chỉ có thể đợc kiến tạo bằng cách gọi một trong những phơng thức kiến tạo của lớp .
Sau khi tạo các đối tợng của lớp, khách hàng có thể kích hoạt các phơng thức thể hiện của lớp, sử dụng dấu chấm phân cách:
Object. Method (arguments)
Trong ngôn ngữ hớng đối tợng, việc này đợc mệnh danh là gửi một thông điệp (message) tới một đối tợng, và đối tợng đợc gọi là ngời nhận thông điệp (receiver).
Chẳng hạn, hai lời gọi phơng thức hợp lệ của lớp Time:
Time dusk = dawn.addMinute(60*12);
Dusk (printTime);
ở đây, dawn là ngời nhận thông điệp addMinute và dusk là ngời nhận thông điệp printTime.
Chúng ta cùng xem một số minh hoạ. Đầu tiên, thông điệp addMinute đợc gửi tới đối tợng đợc tham chiếu bởi dawn:
Lớp Time
hour 5
minute 35
dawn
Hình 5.5: Biến dawn sau khi khởi tạo
hour 5
minute 35
dawn
class Time
{
private int hour, minute;
public Time(int h, int m)
{
hour = h;
minute = m;
}
public Time addMinute(int m)
{
int totalMinutes = (60 * hour + minute + m) % (24 * 60);
if totalMinutes < 0
totalMinutes = totalMinutes + (24 * 60);
return new Time (totalMinutes/60, totalMinute%60);
}
public void printTime()
{
if (hour == 0) && (minute == 0)
System.out.print("Nửa đêm!");
else if (hour == 12) && (minute == 0)
System.out.print("Giữa tra!"); else
if (hour == 0 ) System.out.print(12);
else if (hour > 12) System.out.print(hour-12);
else System.out.print(hour);
if (minute <19) System.out.print(":0" + minute);
else System.out.print(":" + minute);
if (hour <12) System.out.print("AM");
else System.out.print("PM");
}
}
}
Bây giờ chúng ta trở lại công việc lập trình lớp Time. Mã đầy đủ của Time đợc cho ở trên.
Phần mô tả (declarations) của một lớp chỉ ra đại diện (representation) của các đối tợng của lớp đó, dới dạng các biến thể hiện (và các hằng tợng trng). Lớp Time có mô tả sau:
private int hour, minute;
với mô tả này, chúng ta nói rằng lớp Time có hai thuộc tính nguyên riêng biệt là hour và minute.
Bổ từ private chỉ ra rằng các biến thể hiện hour và minute chỉ có thể đợc truy cập bởi các phơng thức bên trong lớp Time. Cũng vậy, bổ từ public xác định các biến thể hiện có thể đợc truy cập và sửa đổi boỉ mã bên ngoài lớp. Nguyên tắc của lập trình hớng đối tợng là cac biến thể hiện sẽ là private. Trong một chơng trình đợc thiết kế tốt hiếm khi phải dùng các biến public . Xem phần sau.
Các phơng thức kiến tạo (constructors) cho Java biết cách khởi tạo các đối tợng mới của lớp. Một phơng thức kiến tạo thực tế chính là một phơng thức đặc biệt: nó có tên trùng với tên lớp và không có giá trị trả về (kể cả kiểu void). Cũng nh các biến thể hiện, các phơng thức kiến tạo sẽ đợc mô tả là private hoặc public (hay dùng). Thờng cần có một số tham số dùng để khởi tạo đối tợng.
Chúng ta đã quyết định lớp Time sẽ có một phơng thức kiến tạo với hai tham số nguyên dùng để khơỉ tạo hai biến thể hiện:
public Time(int h, int m){hour = h; minute = m;}
Để hoàn thành phần định nghĩa lớp, chúng ta cần định nghĩa riêng hai phơng thức addMinute và printTime.
Một phơng thức thể hiện của một lớp C luôn luôn có một đối số kiểu C. Đó là ngời nhận thông điệp khi phơng thức đợc kích hoạt. Tham số này không có tên rõ ràng trong danh sách các tham số của phơng thức, song nó đợc hàm ý (implicit), Điều này có nghĩa là định nghĩa của addMinute sẽ có dạng nh sau (Chú ý rằng nó trả về một giá trị kiểu Time)
public Time addMinute (int m){}
Không có tham số kiểu Time nào đợc đề cập, mặc dù chúng ta biết rằng phơng thức addminute có một ngời nhận thông điệp kiểu Time bổ sung vào cùng với tham số nguyên của nó. Tơng tự:
public void printTime (){}
Do chỉ có một tham số Time và là tham số đợc hàm ý, phơng thức đợc mô tả là không có tham số.
Cả hai phơng thức đều là public, có nghĩa là các khách hàng của lớp Timecó thể gọi đến chúng. Không có lý do gì để định nghĩa một lớp không có một phơng thức
public nào; trên thực tế, thờng là tất cả các phơng thức (và phơng thức kiến tạo) của lớp đều là public. Trong một số trờng hợp, chúng ta muốn một phơng thức chỉ khả dùng bởi các phơng thức khác trong một lớp (không khả dùng với các khách hàng bên ngoài lớp.) Khi đó chúng ta mô tả là private.
Khi viết các phơng thức thể hiện, bạn có thể đặt mình vào hoàn cảnh của ngời nhận thông điệp và “trả lời” cho thông điệp này. Chẳng hạn, khi viết printTime, bạn có thể t duy theo cách nh sau:
Tôi vừa nhận đợc thông điệp printTime, yêu cầu tôi phải in mình ra. Tôi cần xem lại các biến thể hiện hour và minute
của mình để quyết định đang là giữa đêm, giữa tra, hay một thời gian nào khác. Nếu là trờng hợp cuối cùng, tôi sẽ in giờ nh sau: 12 nếu hourlà 0hoặc 12, 1nếu hourlà 1hoặc 13,… Sau đó, nếu hourbé hơn 12, tôi in ra AM, và ngợc lại.
Để tham chiếu tới các biến thể hiện riêng hour và minute, chỉ cần sử dụng mã của chúng. Xem phần mã của printTime ở trên.
Chúng ta trở lại với định nghĩa lớp addMinute. Đối tợng Time nhận thông điệp
addMinute(m) phải trả về một đối tợng Time mới thể hiện thời gian m phút tới trong t- ơng lai. Tuy nhiên, m có thể là một số âm, trả về thời gian trong quá khứ; m cũng có thể rất lớn (vài ngày.) Khả năng này khiến phơng thức trở nên phức tạp hơn là bạn có thể dự đoán.
Cách đơn giản nhất để tính toán thời gian mới là:
public Time addMinute (int m)
{
int totalMinutes = (60 * hour + minute + m);
return new Time (totalMinutes/60, totalMinute%60);
Chú ý cách mà một đối tợng Time đợc kiến tạo và trả về nh là kết quả của lời gọi phơng thức. Nhân tiện, bạn cũng biết cách sử dụng một biến địa phơng, totalMinutes, đợc bổ sung vào tập các biến thể hiện.
Có hai vấn đề nảy sinh. Một là, nếu m quá lớn, giá trị của totalMinutes/60 có thể vợt quá 23 và vì vậy không phaỉ là một giờ hợp lệ. Hai là, nếu m là số âm, totalMinutes có thể cũng âm, từ đó giờ và phút đều là số âm. Vấn đề thứ nhất có thể đợc giải quyết bằng cạc giới hạn totalMinutes/60 phải nằm giữa –(24*60) và (24*60). Vấn đề thứ hai có thể giải quyết bằng cách cộng thêm 24*60 vào totalMinutes/60, nếu âm. Xem đoạn mã của phơng thức này.
Nh vậy chúng ta đã xem xét những thành phần cơ bản của các lớp- các biến thể hiện, các phơng thức kiến tạo, và các phơng thức thể hiện. Chúng tôi sẽ giới thiệu những tính năng khác của lớp trong các chơng tiếp theo. Chúng ta kết thúc chơng này với một trong số đó: các phơng thức kiến tạo nạp chồng (overload).
Thờng là có lợi khi sử dụng hai hoặc nhiều hơn các phơng thức kiến tạo phân biệt nhau bởi số lợng hay kiểu của các đối số; và tính năng này là cho phép trong Java. Chúng ta sẽ bổ sung thêm hai phơng thức kiến tạo nữa cho lớp Time:
class Time
{
private int hour, minute;
public Time (int h, int m) {hour = h; minute = m}
public Time () {hour = 0; minute = 0;}
public Time (int m) {hour = m/60; minute = m%60;}
...
}
Phơng thức kiến tạo thứ hai thiết đặt thời gian của thể hiện mới về “giữa đêm”, phơng thức thứ ba hiểu đối số của nó là một số nguyên và thiết đặt thời gian của thể hiện về thời điểm tơng ứng vơí số phút chứa trong tham số này.
Với các phơng thức kiến tạo trên đây, các mô tả:
Time t1 = new Time(),
t2 = new Time(720),
t3 = new Time(525);
là tơng đơng với:
Time t1 = new Time(0, 0),
t3 = new Time(8, 45);
Do đối với mỗi phơng thức kiến tạo chúng ta có số lợng các đối số khác nhau, Java có thể biết phơng thức nào mà bạn muốn dùng bằng cách nhìn vào số lợng các tham số mà bạn cung cấp. (Chú ý rằng khi sử dụng phơng thức kiến tạo không có tham số, bạn vẫn cần phải đa các dấu ngoặc đơn vào lời mô tả của nó.)
Các phơng thức Time bổ sung
Tập các thao tác của chúng ta trong lớp Time còn quá nghèo nàn, vì vậy hãy làm giàu cho nó.
Hoặc giả, một phơng thức trừ giờ:
t.subMinute(i)
cho kết quả tơng tự t.addMinute(-i).
Một khách hàng có thể gọi t.addminute(-i) thay vì subMinute(i), song chúng tôi thích dùng cách thứ hai nh là một sự pha chế tiện dụng hơn.
T tởng là ở chỗ: mã của subMinute là một lời gọi addminute:
public Time subMinute(int m)
{
return ngời-nhận-subMinute.addMinute(-m); }
Trong đó ngời-nhận-subMinute là một đối tợng Time đã nhận lời gọi này của subMinute. Tuy nhiên, chúng ta không có cách nào để định danh ngời nhận của một phơng thức ngay trong bản thân phơng thức đó. Vậy thì làm thế nào ?
Trong thân mã của một phơng thức nh subMinute, một phơng thức khác cùng lớp (nh addMinute) có thể đợc gọi với cùng một ngời nhận (receiver) bằng cách gọi nó mà không với một ngời nhận rõ ràng nào. Phơng thức trở thành:
public Time subMinute (int m)
{
return addMinute(-m);
}
Nếu t là biến kiểu Time và khách hàng gửi thông điệp subMinute tới t với tham số n (t.subMinute(n)), thì subMinute sẽ gửi thông điệp addMinute tới t với tham số -n.
now = now.subMinute(18);
trong một khách hàng của Time. Giả sử now là biến đang chứa thời gian 1:30 PM.
hour 13
minute 30