Nắm vững lập trình hướng đối tượng nguyên mẫu
Thế giới của các đối tượng
Khi bn bắt đầu một ngày làm việclái xe đến cơ quan, ngồi tại bàn để thực hiện một nhiệm vụ,
ăn một bữa ăn, đi bộ qua một công viên—bn thường có thể thao tác và tương tác với thế giới
của mình mà không cn biết các định luật vật lý chi tiết chi phối . Bạn có thể xlý các hệ
thống khác nhau mà bn đối phó với chúng mỗi ngày như là các đơn vị hoặc các đối tượng. Bạn
đánh giá tính phức tạp của chúng và thay vào đó nên tập trung vào các tương tác của bạn với
chúng.
Lịch sử
Simula, một ngôn ngữ để mô hình hóa, thường được coi là ngôn ngữ hướng đối tượng đầu tiên.
Sau đó, đến Smalltalk, C++, ngôn ngữ Java và C#. Tại thời điểm đó, trong hầu hết các ngôn ngữ
hướng đối tượng, các đối tượng được định nghĩa theo lớp. Sau đó, các nhà phát triển ngôn ngữ
lập trình Self, mt hệ thống ging như Smalltalk, đã tạo ra một phương thức thay thế và trọng
lượng nhhơn để định nghĩa các đối tượng được gọi là lập trình dựa trên nguyên mẫu hoặc lập
tnh hướng đối tượng nguyên mẫu.
Cuốing, JavaScript được phát trin với mt hệ thống đối tượng dựa trên nguyên mẫu
(prototype). Tính phbiến của JavaScript đã đưa các đối tượng dựa trên nguyên mẫu vào dòng
chảy chính. Mặc dù nhiều nhà phát trin cảm thấy khó chịu về điều này, nhưng nhờ vào việc
kiểm tra chặt chẽ hơn nên các hệ thống dựa trên nguyên mẫu có nhiều lợi thế.
Lập trình hướng đối tượng (Object-oriented programming - OOP) cố gắng to ra các hệ thống
phần mềm hoạt động mt cách mô phỏng, là k thuật mô hình hóa mạnh mẽ và phổ biến rộng rãi
trong phát trin phần mm. Lập trình hướng đối tượng phổ biến vì nó phản ánh cách chúng ta
nhìn thế giới: như là một tập các đối tượng có thể tương tác với nhau và được thao tác theo nhiều
cách khác nhau. Sức mạnh của lập trình OOP nằm trong hai nguyên tắc cốt lõi của nó:
Tính bao đóng (Encapsulation)
Cho phép các nhà phát trin che giấu các hoạt động bên trong của cấu trúc dữ liệu và ch
l ra các giao din (interface) tin cậy có thể được dùng để tạo module hay các phần mềm
gắn kết. tương tnhư việc ẩn dấu thông tin.
Tính kế thừa (Inheritance)
Mở rộng sức mnh của sự đóng gói bằng cách cho phép các đối tượng thừa kế các hành
vi đã đóng gói của các đối tượng khác. Nó tương tự như việc chia sẻ thông tin.
Hầu hết các nhà phát trin đều biết rõ nhng nguyên tắc này vì mi nn ngữ lập trình chính
thống đều hỗ trợ OOP (và thực hin nó trong nhiều trường hợp). Mặc dù tt cả các ngôn ngữ
OOP đều hỗ trợ hai nguyên tắc cốt lõi, dưới dạng này hay dng khác, trong nhiều năm qua đã
ít nhất hai cách khác nhau cơ bản về định nghĩa các đối tượng.
Trong bài này, hãy tìm hiểu về những li ích của các mẫu lập trình OOP nguyên mẫu và các mẫu
đối tượng JavaScript.
Về đầu trang
Nguyên mẫu-là gì? Các lp và các nguyên mẫu
Một lớp (class) mt khái niệm trừu tượng về các đối tượng định nghĩa các cấu trúc dữ liệu và
các phương thức bên trong nó hoặc là một tập hợp các đối tượng. Mỗi đối tượng là mt thể hin
(instance) của lớp. Các lớp ng có nhiệm vụ xây dựng các đối tượng lớp theo các định nghĩa
của chúng và (tùy chọn) theo các tham số người dùng.
Một dụ cổ điển là lớp Point và lớp con của nó là Point3D để định nghĩa các điểm hai chiều
ba chiều, tương ứng. Liệt kê 1 (Listing 1) cho thấy các lớp trông như thế này theo mã Java.
Liệt kê 1. Lớp Point của Java
class Point {
private int x;
private int y;
static Point(int x, int y) {
this.x = x;
this.y = y;
}
int getX() {
return this.x;
}
int getY() {
return this.y;
}
void setX(int val) {
this.x = val;
}
void setY(int val) {
this.y = val;
}
}
Point p1 = new Point(0, 0);
p1.getX() // => 0;
p1.getY() // => 0;
// Lớp Point3D là con của lớp Point, kế thừa các đặc tính của Point
class Point3D extends Point {
private int z;
static Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
int getZ() {
return Z;
}
void setZ(int val) {
this.z = val;
}
}
Point3D p2 = Point3D(0, 0, 0);
p2.getX() // => 0
p2.getY() // => 0
p2.getZ() // => 0
Ngược với định nghĩa đối tượng theo lớp, các hệ thống đối tượng nguyên mẫu hỗ trợ mt
phương thức tạo đối tượng trực tiếp hơn. Ví dụ, trong JavaScript mt đối tượng là mt danh sách
các đặc tính đơn gin. Mỗi đối tượng có liên quan mật thiết với đối tượng cha khác hoặc nguyên
mẫu, đối tượng mà từ đó nó được thừa kế các đặc tính. Bạn có thể bắt chước ví dụ Point trong
JavaScript, như trong Liệt kê 2.
Liệt kê 2. Lớp Point của JavaScript
var point = {
x : 0,
y : 0
};
point.x // => 0
point.y // => 0
// tạo một đối tượng mới từ nguyên mẫu Point, kế thừa các đặc tính của Point
point3D = Object.create(point);
point3D.z = 0;
point3D.x // => 0
point3D.y // => 0
point3D.z // => 0
mt sự khác biệt cơ bản giữa các hệ thống đối tượng nguyên mẫu và cổ điển. Các đối tượng
cđin được định nghĩa một cách trừu tượng như là một phần của một nhóm khái niệm và thừa
kế những đặc tính từ các lớp hoặc các nhóm khác của các đối tượng. Ngược li, các đối tượng
nguyên mẫu được định nghĩa cụ thể là các đối tượng cụ thể và thừa kế hành vi từ các đối tượng
cụ thể khác.
Vì vậy, một ngôn ngữ OOP dựa trên lp mt tính chất kép, đòi hi ít nhất là hai cấu kin cơ
bản: các lớp và các đối tượng. Nhưmt kết quả của tính hai mặt này, khi phần mềm dựa trên
lp phát trin, các hệ thống phân cấp lớp phức tạp có xu hướng phát trin. Nói chung không thể
dự báo được tất cả các cách mà các lớp sẽ cần được sử dụng các cách đó trong tương lai, vì thế,
h thống phân cấp lớp cần được liên tục cấu trúc lại để tạo điều kiện thuận lợi cho các thay đổi.
Các ngôn ngữ dựa trên nguyên mẫu loại bỏ sự cần thiết về tính hai mặt nói trên và tạo điều kiện
thuận lợi cho việc tạo và xử lý trực tiếp các đối tượng. Nếu như không có các đối tượng bị ràng
buộc bởi lớp, có thể tạo ra các hệ thống các đối tượng có ràng buc lỏng lẻo hơn để trợ giúp duy
t tính mô đun và làm giảm sự cần thiết phải cấu trúc lại.
Ngoài ra, việc có thể trực tiếp định nghĩa các đối tượng sẽ b sung khả năng to lớn và tính d
hiểu cho việc tạo và xử lý đối tượng. Ví dụ, trong Liệt kê 2, bạn có thể chỉ cn khai báo đối
tượng point của mình với một dòng: var point = { x: 0, y: 0 };. Với một dòng này, bạn
một đối tượng làm việc đầy đủ, thừa kế hành vi tObject.prototype của JavaScript, chẳng
hạn như phương thức toString. Để mở rộng hành vi của đối tượng của mình, bạn chỉ cần khai
o một đối tượng khác với point là nguyên mẫu của nó. Ngược li, ngay cả với ngôn ngữ OOP
cđin ngắn gọn nhất, trước tiên bạn sẽ phải định nghĩa mt lớp, sau đó khởi tạo nó trước khi
bạn một đối tượng có thể thao tác được. Để thừa kế, bạn sẽ phải định nghĩa một lớp khác để
m rộng lớp đầu tiên này.
Mẫu của nguyên mẫu là khái niệm còn đơn giản hơn. Là con nời, chúng ta thường nghĩ về các
nguyên mẫu. Ví dụ, trong mt bài viết trên blog của Steve Yegge "Các mẫu thiết kế đa năng"
(xem phần Tài nguyên), ông trích dẫn ví dụ của một cầu thủ bóng đá Mỹ—là Emmitt Smith—
người mà với tc độ của mình, sự nhanh nhẹn và sức mnh di chuyển trở thành nguyên mẫu cho
tt cả các cầu thủ bóng đá mới trong Liên đoàn bóng đá quốc gia (NFL). Sau đó, khi mt cầu thủ
tấnng mới nổi là LT được chọn, các nhà bình luận nói rằng:
"LT có cặp giò ging Emmitt."
"Anh ấy có thểy như Emmitt."
"Tuy nhiên, anh y chạy mt dặm chỉ trong năm phút!"
Các nhà bình luận đang mô hình hóa một đối tượng mớilà LT—dưới dạng mt đối tượng
nguyên mẫu, là Emmitt Smith. Trong JavaScript, mt mô hình như vậy sẽ trông ging như Liệt
kê 3.
Liệt kê 3. Mô hình JavaScript
var emmitt = {
// ... khai báo các đặc tính (properties)
};
var lt = Object.create(emmitt);
// ... khai báo các đặc tính khác cho đối tượng lt
Bạn có thể đi chiếu ví dụ này với việc mô hình hóa cđin, ở đó bạn thể định nghĩa một lớp
RunningBack (cầu thủ tấn công) thừa kế từ lớp FootballPlayer (cầu thủ bóng đá). Lớp lt và
emmitt sẽ là các thể hiện của lớp RunningBack. Trong mã Java các lớp này có thtng như Liệt
kê 4.
Liệt kê 4. Ba lớp Java
class FootballPlayer {
private string name;
private string team;
static void FootballPlayer() { }
string getName() {
return this.name;
}
string getTeam() {
return this.team;
}
void setName(string val) {
this.name = val;
}
void setTeam(string val) {
this.team = val;
}
}
class RunningBack extends FootballPlayer {
private bool offensiveTeam = true;
bool isOffesiveTeam() {
return this.offensiveTeam;
}
}
RunningBack emmitt = new RunningBack();
RunningBack lt = new RunningBack();
hình cđin đi kèm với chi p hoạt động dựa trên khái niệm nhiều hơn đáng kể, nhưng
không có skim soát mượt mà trên các thể hin lớp emmitt lt mà bn nhận được với mô
hình nguyên mẫu. (Công bằng mà i, lớp FootballPlayer không cần thiết 100%; nó có mặt
đây để so sánh với ví dụ tiếp theo). Đôi khi, chi p hoạt động này thể có ích, nhưng thường
nó ch là gánh nặng ti.
Khá dễ dàng để mô phỏng mô hình hóa c điển với một hệ thống đối tượng nguyên mẫu. (Phải
thừa nhn rằng, cũng có thể làm ngược lại, mặc dù có lẽ không dễ dàng gì). Ví dụ, bạn thể tạo
ra mt đối tượng footballPlayer với một đối tượng runningBack khác có thừa kế t